Verifying a payment

Transfer-status semantics, finality on Solana, and recommended polling cadence for SDP transfers.

When a payment lands on a wallet you control, the inbound transfer moves through a handful of status states. This page describes those states, what each one means about on-chain finality, and how to decide when it is safe to ship goods or release funds.

Status lifecycle

The status field on the Transfer record takes one of five values:

StatusMeaning
pendingPrepare-mode outbound transfers — POST /v1/payments/transfers/prepare saves the serialized transaction and the record stays pending until the client submits the signed tx (then it moves to processing) or the blockhash expires (then the track-pending-transfers job marks it failed). No signature yet.
processingThe transfer is in flight. For Execute-mode this is the initial state at record creation: the request has been accepted and the transaction is being built and submitted; the signature field is not yet populated when the record is first written. Prepare-mode transfers also enter processing once the signed transaction is submitted.
confirmedThe cluster has confirmed the transaction. signature and slot are populated. blockTime may also be set, but is not guaranteed at this stage — treat it as optional until finalized.
finalizedThe slot containing the transaction is locked in by the network — finality at the Solana cluster's strongest guarantee. signature and slot are populated; blockTime is best-effort and may remain null for Execute-mode transfers whose finalization is observed by the track-pending-transfers job (which updates status and slot but does not backfill block time). When you need a definitive timestamp, resolve it from the on-chain signature via Solana RPC. Safe to treat as irreversible.
failedThe transaction was built and submitted but rejected by the network (insufficient funds, frozen account, expired blockhash, etc.). The error field describes what reached the chain.

A transfer moves forward through processing → confirmed → finalized for Execute-mode, or pending → processing → confirmed → finalized for Prepare-mode (with pending → failed if the blockhash expires before submission). Inbound transfers discovered via on-chain signature history surface directly at confirmed (or failed if the on-chain transaction errored) — they never appear as pending. Any state can transition to failed; failed is terminal.

Finality on Solana

For real-money flows, treat finalized as the safe-to-act-on signal. confirmed is durable enough for most non-adversarial workflows (think: showing a "paid" badge in your dashboard) but a sufficiently motivated cluster reorganization can theoretically roll back a confirmed transaction; this does not happen for finalized transactions.

Use confirmed to release low-stakes side effects (status emails, UI updates) and finalized for irreversible side effects (releasing custody, shipping high-value goods, posting to your general ledger).

Polling cadence

SDP does not currently emit settlement webhooks. Status is observed by polling:

  • GET /v1/payments/transfers/{id} for a single transfer's current state.
  • GET /v1/payments/transfers?direction=inbound&from=… for a windowed list — see Indexing and reconciliation.

Cadence depends on how quickly you need to see state transitions and how aggressive your retry budget is. A reasonable starting range:

  • Active checkout (customer is staring at a payment screen): every 2–5 seconds for up to a minute, then back off.
  • Background reconciliation (no one is waiting): every 15–30 seconds, or whenever a worker tick fires.
  • Long-tail catch-up (status reads after the fact): single one-shot reads when your application needs to act, no polling loop at all.

The exact recommended cadence will be tightened in a follow-up once the backend confirms a target; treat the ranges above as defensible defaults today.

Independent verification

Every transfer that reaches processing or beyond carries enough information to verify against the network directly:

  • signature — the Solana transaction signature; paste into Solscan or Solana Explorer to see the on-chain record.
  • slot — the slot the transaction landed in.
  • blockTime — ISO 8601 timestamp of slot finality.
  • fee — fee paid, in lamports.

If you maintain your own indexer (getSignatureStatuses against a Solana RPC), cross-reference SDP's signature to your indexer's view to confirm SDP's status reflects what your nodes see.

Risk metadata

When a risk-screening provider is configured on the organization, transfers may carry a risk field with provider, score, level (low | medium | high | unknown), and evaluatedAt. Use this to gate downstream side effects on a risk threshold; the field is absent when no risk provider is wired up.

Risk evaluation is not blocking by default — a transfer can complete with a high-risk score. If you want a transfer to halt on risk threshold, enforce that in your application logic on top of the risk.level value.

Failure handling

When status settles to failed:

  • Read the error field. SDP returns the chain-level error message; common causes are insufficient source funds, account frozen by token authority, or stale blockhash.
  • Decide whether to retry. The original transfer record is terminal; a retry creates a new transfer with a new ID and (eventually) a new signature.
  • Surface the failure to whoever needs to know — the merchant, the customer, your operations team.
Is this page helpful?