Skip to main content
Driftstack DRIFTSTACK

Crypto orders — ops runbook

This page documents the support workflow for the crypto- payments surface. It's published publicly because the non-refundable policy means every decision support takes is consequential, and customers deserve to read the same playbook the support team follows.

"Here's a NowPayments payment id — what order is that?"

The fastest reverse-lookup path: the admin list endpoint accepts an exact-match payment_id query param.

GET /v1/admin/crypto-orders?payment_id=np_abc123
Authorization: Bearer $ADMIN_KEY

Returns the (at most one) matching order. Available in the admin GUI as the "Payment ID" filter input next to the search box. Distinct from the fuzzy search param (which walks order_id / product / customer_note) so the lookup is O(scan) but unambiguous.

"My order is stuck in pending"

  1. Pull the order: GET /v1/admin/crypto-orders/:id as an admin key.
  2. Check the age. Pay windows are ~60 minutes. Orders past that without an IPN are candidates for sweep (POST /v1/admin/crypto-orders/sweep-expired).
  3. If the customer says they paid, ask for the chain + tx-hash. Cross-reference against NowPayments's dashboard. If the payment landed but the IPN never fired, manually replay the IPN: POST /v1/admin/crypto-orders/:id/apply-ipn with the recorded payment_id + provider_status: 'finished'.
  4. If the payment never landed, sweep the order to failed and explain the pay window.

"I see two charges on my card"

Crypto orders don't touch cards — that's the Stripe surface. Confirm whether the duplicate is crypto-vs-crypto (likely double-click → handled by V-666.AO idempotency keys) or crypto-vs-Stripe (handle on the Stripe side). For two crypto orders on the same customer/product:

  1. Confirm both order ids exist + are both paid.
  2. Check the idempotency-metrics endpoint — does the replays count match what you'd expect from this customer's flow?
  3. If both orders truly settled, this is a customer-side bug (their integration sent two distinct Idempotency-Keys for what they intended as one intent). Crypto is non- refundable; the resolution is to credit the customer's next billing cycle, not refund.

"What happened to this order, in order?"

Pull the timeline:

GET /v1/admin/crypto-orders/:id/events
Authorization: Bearer $ADMIN_KEY

Returns the order's append-only event log oldest-first. Each event carries the destination status, an ISO-8601 timestamp, and a source tag — create, ipn, cancel, expired, or swept. Same timeline is rendered inline on the admin detail drawer.

"Why is my order failed?"

Look at the order's payment_id + the most recent IPN payload. The internal status transitions to failed in three cases:

Server-side, the crypto.order.failed event emitted by these transitions carries a reason field with one of ipn / expired / swept. This event is customer-subscribable via POST /v1/webhooks (in SubscribableWebhookEventTypeSchema as of 2026-05-22, migration 0064); see /docs/webhooks-crypto-events for the payload contract. Ops can read the same source via the /events endpoint above for retrospective audit.

"How do I bulk-export today's orders for reconciliation?"

Use /docs/admin-csv-export from the admin GUI, or hit the CSV endpoint directly. For anything beyond 1000 rows, walk the cursor-paginated JSON list (cursor docs).

V-666.BY: scope the export to a date window with ?created_after + ?created_before (ISO 8601). Both work on the JSON list, the CSV endpoint, and the admin GUI's From/To inputs. Combine with ?status=paid for a nightly paid-only reconcile.

GET /v1/admin/crypto-orders.csv?status=paid&created_after=2026-05-01T00:00:00Z&created_before=2026-05-12T00:00:00Z
Authorization: Bearer ds_admin_…

"A customer's idempotency-key is misbehaving"

Check the body_mismatches counter on /v1/admin/crypto-orders/idempotency-metrics. A non-zero number means at least one client is reusing keys across distinct intents — usually a hardcoded constant where a generated UUID belongs. The structured warn log (event: 'crypto_checkout_idempotency_body_mismatch') carries the account id; grep for the offending integration.

Related