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#
// ❌ BAD
const apiKey = 'api-3f9a...';
// ✅ GOOD
const apiKey = process.env.AUXCORE_API_KEY;Always Use HTTPS#
// ❌ 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.
// ✅ 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.
// ✅ 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/transactiondoes not passIdempotency-Keythrough. Supply anOrderIdfor reconciliation, but it is not unique in the database and does not deduplicate charges. After a timeout or5xx, look upGET /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#
// ✅ 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.
// ✅ 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#
// ✅ 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#
// ❌ 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#
// ❌ 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#
// ❌ BAD
alert('Error');
// ✅ GOOD
alert('Payment declined. Please try a different card or contact your bank.');Validate Before Submitting#
// ✅ 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#
// ✅ Always send receipt
if (status === 200 && body.status === 'success') {
await sendReceipt(customer.email, transaction);
showSuccessMessage('Payment successful!');
}📈 Monitoring#
Log Important Events#
// ✅ 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#
// ✅ 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#
// ✅ 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#
// ✅ 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