Verify Checkout Docs

Turn outcomes into one ledger entry

Use webhooks for low-latency notification and the deposit API as the current source of truth. A scheduled poller closes gaps caused by downtime or exhausted retries.

PathBest useRequired protection
WebhookPrompt processingSignature verification and event deduplication
GET /v1/deposits/{id}Current state lookupAPI authentication and rate-aware retry
GET /v1/depositsPeriodic sweepCursor/checkpoint and bounded time windows
GET /v1/deposits/{id}/eventsInvestigationTreat as history, not a second credit trigger

Credit exactly once

Use the Verify Checkout deposit ID as a unique ledger key inside the same database transaction that credits your customer.

await database.transaction(async (transaction) => {
  const inserted = await transaction.creditEvents.insertIfAbsent({
    externalId: deposit.id,
    amount: deposit.amount,
    currency: deposit.currency,
  });

  if (inserted) await transaction.wallets.credit(deposit.merchant_customer_id, deposit.amount);
});

succeeded is the crediting outcome. Failed and expired deposits must not be credited. Hold review outcomes for operational resolution. For any unfamiliar or non-terminal state, store it and wait rather than assuming success.

Recovery loop

  1. Verify and durably enqueue each webhook before returning 2xx.
  2. Deduplicate by event ID and apply ledger changes by unique deposit ID.
  3. Retrieve the deposit before acting when events arrive out of order.
  4. Periodically list unresolved deposits and poll their current state.
  5. Use the event timeline and meta.requestId to investigate discrepancies.

Retries are normal

At-least-once delivery means repeated events are expected, not exceptional. Correctness comes from idempotent storage, not from assuming one delivery.

On this page

No Headings