Skip to main content
Driftstack DRIFTSTACK

Detecting order state changes

A crypto order's status moves between pendingconfirmingpaid (or failed / partial / cancelled). Two customer-facing patterns are supported: poll GET /v1/billing/crypto-orders, or subscribe a webhook endpoint to the now-live crypto.order.paid / crypto.order.failed events. See /docs/webhooks-crypto-events for the event payload contract.

Today: polling

Polling stays a first-class path for backfill, reconciliation, and any environment where you cannot accept an inbound HTTPS callback. Both crypto.order.paid and crypto.order.failed are also subscribable via POST /v1/webhooks — see the hybrid section below for the recommended pattern.

// Watch a single order until it settles.
for (;;) {
  const order = await client.cryptoOrders.get(orderId);
  if (order.status === 'paid' || order.status === 'failed' ||
      order.status === 'partial' || order.status === 'cancelled') {
    break;
  }
  await sleep(5_000);
}

Polling cadence

Pick a cadence that matches your latency tolerance:

Combining cursor pagination + status filter

For backfill, scan only the orders you care about by combining ?status=paid with cursor pagination:

for await (const o of client.cryptoOrders.listAll({
  status: 'paid',
  limit: 100,
})) {
  await reconcile(o);
}

The SDK's listAll() manages cursors internally and yields one envelope at a time, so consumers can break early without paying for the full scan.

What the customer dashboard does

The Driftstack customer dashboard polls GET /v1/billing/crypto-orders every 60s while any visible order is still pending (V-534.BS). Polling stops automatically as soon as everything settles. For an SDK-driven checkout flow that needs faster feedback, shorten the poll interval to 5-10s — the rate-limit budget on the customer list endpoint is generous enough for that pattern.

Recommended: hybrid (webhooks + reconciliation polling)

Both crypto.order.paid and crypto.order.failed are in SubscribableWebhookEventTypeSchema. The recommended pattern is hybrid: webhooks for sub-second notification, periodic polling as a safety net for the rare delivery that does not land within the retry window.

// Subscribe both terminal-state events.
await client.webhooks.create({
  url: 'https://example.com/webhooks/driftstack',
  events: ['crypto.order.paid', 'crypto.order.failed'],
});

app.post('/webhooks/driftstack', verifySignature, async (req, res) => {
  const event = req.body;
  if (event.type === 'crypto.order.paid') {
    await db.markOrderPaid(event.data.order_id);
  } else if (event.type === 'crypto.order.failed') {
    await db.markOrderFailed(event.data.order_id);
  }
  res.status(200).end();
});

Idempotency for hybrid receivers

Webhook delivery for crypto.order.* follows the same retry policy as the rest of the surface, so receivers must be idempotent — the same event may be delivered more than once. Key your local DB upsert on (order_id, status) so duplicate crypto.order.paid events for the same order are a no-op. See /docs/webhooks for the signing scheme + retry schedule shared across all subscribable event types.

Related