Developer dashboard

Best Practices#

Production-ready best practices for AuxCORE integration. Examples use the auxcore(method, path, body) fetch helper from the Node.js examples.


🔐 Security#

Never Hardcode Credentials#

javascript
// ❌ BAD
const apiKey = 'api-3f9a...';

// ✅ GOOD
const apiKey = process.env.AUXCORE_API_KEY;

Always Use HTTPS#

javascript
// ❌ BAD
const baseUrl = 'http://app.auxcore.net';

// ✅ GOOD
const baseUrl = 'https://app.auxcore.net';

Verify Webhook Signatures#

Copy verifySignature() from the Node.js webhook example into your server helper and use it for every delivery. It rejects missing or malformed headers, invalid timestamps and non-hex signatures without throwing.

javascript
// ✅ ALWAYS verify signatures — against the RAW request body.
// X-Webhook-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, `${t}.${rawBody}`)>
if (!verifySignature(rawBody, req.headers['x-webhook-signature'], secret)) {
  return res.status(401).send('Invalid signature');
}

Preserve the raw body before JSON parsing. A valid signature does not prevent duplicate deliveries: atomically store each event.id in a durable inbox and enqueue work once. See the integration guide handler.

Never Send Raw Card Data#

Collect cards with the PCI SDK (https://vault.auxcore.net/sdk/auxVault.js) and send only the pci_tok_… token. Raw CardNumber is rejected on Sandbox and Production with 400 PCI_SCOPE_VIOLATION.


💻 Code Quality#

Use Idempotency Keys#

Create and persist a payment-attempt UUID before sending the first request. Reuse its key and the exact payload for every retry of that attempt, including recovery after a timeout or process restart. Create a new attempt only after the earlier outcome is known and a new payment is intentionally requested.

javascript
// ✅ Prevent duplicate charges on the admin sale endpoint (key: 16–255 chars)
// paymentAttemptId is a UUID loaded from your database, not regenerated for a retry.
const idempotencyKey = `payment-${paymentAttemptId}`;
const requestBody = JSON.stringify({ merchantId, amount: 100.00, pciToken });

async function submitPaymentAttempt() {
  return fetch('https://uat.auxcore.net/api/v1/transactions/sale', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${jwt}`,
      'X-Tenant-ID': tenantId,
      'Idempotency-Key': idempotencyKey,
      'Content-Type': 'application/json',
    },
    body: requestBody,
  });
}

const response = await submitPaymentAttempt();

The middleware caches responses for 24 hours. 409 IDEMPOTENCY_REQUEST_IN_PROGRESS means the attempt is still running; wait and retry with the same key and payload. 422 IDEMPOTENCY_KEY_REUSED means the payload differs: reconcile the existing attempt instead of silently generating a new key. Cached responses carry Idempotent-Replayed: true.

Public API: POST /api/v1/public/transaction does not pass Idempotency-Key through. Supply an OrderId for reconciliation, but it is not unique in the database and does not deduplicate charges. After a timeout or 5xx, look up GET /api/v1/public/transaction-list?orderId=… and match the results to your attempt. An empty immediate lookup is not proof of failure; wait for the webhook or confirm with the processor before authorizing another charge.

Handle All Errors#

javascript
// ✅ Comprehensive error handling
const { status, body } = await processPayment();
if (status === 402) {
  // Declined — show body.message, inspect body.data.ResponseCode
} else if (status === 429) {
  // Rate limited — retry with backoff (see RateLimit-Reset header)
} else if (status >= 400) {
  // Log body.error and alert
}

Use Exponential Backoff#

Use automatic backoff for read requests or writes protected by idempotency. For protected writes, preserve the key and payload. Do not wrap public payment, void or refund submissions in a generic retry loop when their outcome is unknown; follow the reconciliation steps above.

javascript
// ✅ Smart retry logic
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (!isRetryable(error) || i === maxRetries - 1) {
        throw error;
      }
      await sleep(Math.min(1000 * Math.pow(2, i), 10000));
    }
  }
}

📊 Performance#

Cache Static Data#

javascript
// ✅ Cache payment methods, merchants, etc.
const cache = new Map();

async function getCustomer(id) {
  if (cache.has(id)) return cache.get(id);

  const { body } = await auxcore('GET', `/customer/${id}`);
  const customer = body.data;
  cache.set(id, customer);
  return customer;
}

Use Webhooks Over Polling#

javascript
// ❌ BAD: Poll every second
setInterval(() => checkStatus(), 1000);

// ✅ GOOD: Process verified events from the durable webhook inbox.
// The receiver verifies signatures and deduplicates event.id before enqueueing.
async function handleQueuedEvent(event) {
  if (event.type === 'transaction.approved') {
    await handleApproval(event.data); // Make order updates and receipts idempotent too.
  }
}

Page Through Lists Instead of Fetching One by One#

javascript
// ❌ BAD: 100 individual requests
for (const txn of transactions) {
  await auxcore('GET', `/transaction/${txn.id}`);
}

// ✅ GOOD: one filtered, paged list (up to `limit` rows per page)
const { body } = await auxcore('GET', '/transaction-list?dateFrom=2026-10-01&dateTo=2026-10-01&page=1&limit=100');
// body.data = rows, body.meta = { page, limit, total, totalPages, summary, statusCounts }

🎯 User Experience#

Show Clear Error Messages#

javascript
// ❌ BAD
alert('Error');

// ✅ GOOD
alert('Payment declined. Please try a different card or contact your bank.');

Validate Before Submitting#

javascript
// ✅ Validate your own fields (amount, email, order id) before calling the API.
// Card number/expiry/CVV are validated inside the PCI SDK's hosted fields.
function validateOrder(order) {
  if (!(order.amount > 0)) return 'Amount must be greater than 0';
  if (order.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(order.email)) return 'Invalid email';
  return null;
}

Provide Payment Confirmation#

javascript
// ✅ Always send receipt
if (status === 200 && body.status === 'success') {
  await sendReceipt(customer.email, transaction);
  showSuccessMessage('Payment successful!');
}

📈 Monitoring#

Log Important Events#

javascript
// ✅ Log all transactions
logger.info('Transaction created', {
  transactionId: body.data.TransactionId,
  amount: body.data.Amount,
  status: body.status,
  responseCode: body.data.ResponseCode
});

Set Up Alerts#

javascript
// ✅ Alert on high error rates
if (errorRate > 0.05) {
  alertOps('High error rate detected');
}

Track Key Metrics#

  • Success rate
  • Average response time
  • Decline rate
  • Webhook delivery rate

💰 Financial#

Reconcile Daily#

javascript
// ✅ Daily reconciliation — meta.summary aggregates the whole filtered set, not just the page
async function reconcileDaily(day) {
  const { body } = await auxcore('GET', `/transaction-list?dateFrom=${day}&dateTo=${day}&limit=1`);
  const ourTotal = await database.getApprovedTotalForDay(day);

  if (Number(body.meta.summary.approvedVolume) !== ourTotal) {
    alertFinance('Reconciliation mismatch');
  }
}

Handle Settlements Properly#

  • Understand settlement schedule
  • Account for holds and reserves
  • Track refunds separately

🧪 Testing#

Test in Sandbox First#

javascript
// ✅ Always test in sandbox
if (process.env.NODE_ENV === 'production') {
  // Production settings
} else {
  // Sandbox settings
}

Test All Scenarios#

  • Approved transactions
  • Declined transactions
  • Network failures
  • Timeout scenarios
  • Webhook failures

📞 Support#

Keep Support Contacts Ready#

  • Technical support email
  • Account manager contact
  • Emergency hotline

Document Your Integration#

  • API endpoints used
  • Webhook configuration
  • Error handling approach
  • Deployment process

Need help? Contact support@auxcore.net