Conversion types
Both types share the same lifecycle and the same webhook event type.
Lifecycle
COMPLETED, FAILED, and RETURNED are terminal. Every other state is transient.
FAILED is reachable from INITIATED, PROCESSING, and PAYOUT_INITIATED. Once the payout is in flight the terminal error is RETURNED instead, because the funds left and are coming back. A successful submit call does not mean a conversion will reach payout, so handle a terminal error out of every transient state.
What COMPLETED means
COMPLETED means Bastion and its payment partner have delivered the funds. It does not mean the receiving bank has credited the recipient’s account. Receiving banks apply their own processing, including compliance screening, so funds can appear in the recipient’s account later than completed_at.
Recovering from a terminal error
FAILED means the payout never went out, so no funds are in flight. Retry if failure_reason points to a transient cause, otherwise route to support.
RETURNED means the payout went out and came back. Retrying against the same payment instructions will usually fail the same way, so have the customer verify the destination bank details and register corrected instructions first.
Treat failure_reason as diagnostic. It reports what the provider or bank said, which belongs in your logs and support tooling rather than in front of a customer.
Values populated during the lifecycle
Some fields have no value until the conversion advances far enough to produce one.
Show quoted figures as estimates, and use the values from the
COMPLETED event on receipts and in your ledger.
Quote lifecycle
International off-ramps must reference a quote. US off-ramps do not use quotes, and aquote_id on a US conversion is rejected.
A quote is created by GET /v2/conversions/quotes, is persisted, and carries an expires_at. It binds fee terms and pricing to the conversion that later references it, which is why fee fields are not accepted on submit.
Three rules govern the binding:
- Single use. A quote supports one successful conversion. Retries that reuse the same
request_idare idempotent and do not consume a second quote. - Expiry. Submitting after
expires_atreturnsQUOTE_EXPIRED. - Consistency. The conversion must match the quote. The
amountmust equal the quotedsource.amount, and the destination corridor must be the one that was priced.
Interpreting webhook events
Conversions emit a single event type,conversion_notification_update, on every state change. The conversion object arrives in data, matching the shape returned by GET /v2/conversions/{conversion_id}.
Two identifiers, used for different things:
- The top-level
ididentifies the event and is regenerated if the event is reprocessed, so don’t use it as your deduplication key. data.idis the conversion ID and is stable across every event for that conversion. Deduplicate business logic ondata.idtogether withdata.status.
INITIATED is the first event on an on-ramp, that event is also the first time you learn the conversion’s ID. Route it using account_id and the Virtual Account it landed against.
For delivery behavior, retries, and sender verification, see Webhooks. For payload field definitions, see Conversion notifications.
Polling
Webhooks are the recommended way to track a conversion. PollGET /v2/conversions/{conversion_id} as a fallback when an expected webhook has not arrived within a reasonable window.
Polling returns current state, not history, and there is no endpoint that lists conversions. Persist each event as it arrives, keyed on data.id, and store identity_id, account_id, and payment_instructions_id alongside your own records. That gives you transition history, event routing, and period reconciliation without querying Bastion.