Skip to content

Production operations

Operate notify, recovery, finality checks, reconciliation, key rotation, and payment incident handling safely.

Production payment UX needs more than a successful pay() response. Build every fulfillment decision so it can survive timeouts, duplicated requests, delayed confirmations, and missed webhooks.

Evidence hierarchy

EvidenceUse it forDo not use it for
Payer client resultImmediate payer UX and evidence uploadIrreversible fulfillment by itself
Notify eventWake a backend verifier quicklyFinancial finality
Signed receiptAuthenticate gateway output and bind contextReplace chain verification
Chain statusConfirm transfer and finalityMerchant order semantics
Merchant databaseIdempotent fulfillment and business policyDeclare a transfer without evidence

Handle a lost success response

If the facilitator broadcast a transaction but the client lost the response, use recoverSettlement. Recovery verifies chain evidence and reconstructs a receipt where possible; it never calls /settle again.

TypeScript
import { recoverSettlement } from 'x402-paysdk/pay';const recovered = await recoverSettlement({ gateway: 'https://pay.example.com', refId, paymentPayload, txHash, signedOffer});if (recovered.recovered) { await submitEvidence(recovered.txHash, recovered.signedReceipt, recovered.receipt);}

Persist the original encoded payment payload only for the shortest operational window required by recovery. It is replayable sensitive material and must never enter logs.

Reconcile from chain history

Run reconciliation on a bounded block range and compare chain transfers with your archived receipts.

TypeScript
import { reconcile } from 'x402-paysdk/merchant';const report = await reconcile(merchant, { payTo, fromBlock: lastReconciledBlock, limit: 200, receipts: archivedPaymentReceipts, refundReceipts: archivedRefundReceipts, minConfirmations: 3, verifyChain: true});for (const anomaly of report.anomalies) { await incidentQueue.enqueue(anomaly);}

Advance your checkpoint to report.lastBlock only after the report and business updates are durably stored.

Rotate credentials

Use independent rotation procedures for API keys and notify keys. Maintain an overlap key ring for notify delivery while both ids can arrive, then explicitly revoke the old key.

TypeScript
import { rotateMerchantKey, revokeOldMerchantKey } from 'x402-paysdk/merchant';const rotated = await rotateMerchantKey(merchant, { keyType: 'notifyHmacKey' });await secrets.storeNotifyKey(rotated.keyId!, rotated.newKey);// After the configured overlap window:await revokeOldMerchantKey(merchant, { keyType: 'notifyHmacKey' });

Operational gates

  • Keep the merchant client in backend-only modules.
  • Use shared atomic nonce storage for notify replay protection.
  • Bound /txs queries, RPC timeouts, response sizes, and retry counts.
  • Keep paymentId stable only for retries of the same exact context.
  • Retry fulfillment writes independently from settlement.
  • Archive the receipt pair and tx hash with the merchant order.
  • Alert on transfers without receipts, receipts without transfers, context mismatches, and refund cumulative mismatches.
  • Keep order mode disabled unless you need gateway-side order authorization and have transactional storage.

Incident rule

When evidence disagrees, stop fulfillment and preserve the original ref snapshot, receipt pair, tx hash, expected order context, and bounded chain response. Never “fix” the mismatch by accepting notify data or issuing a direct wallet transfer.