# Exchange Lifecycle

An **exchange** is a single conversion from one currency to another tracked by Spike from creation through delivery or failure. Every flow in this documentation operates on an exchange, identified by `exchangeId`.

This page is the canonical reference for exchange status values, the partner-visible lifecycle for each direction, and how to read the current state.

## Status values

The `status` field on an exchange is the primary signal for what the partner needs to do next. The same status surface is used for both on-ramp and off-ramp.

| Status | Meaning | Terminal? |
|---|---|---|
| `WAITING_DEPOSIT` | The exchange exists and is waiting for the funding leg to be accepted. On-ramp: initial state while a partner-supplied fiat `depositReference` is processed. Off-ramp: Spike is waiting for a crypto deposit to arrive at the deposit address. | No |
| `DEPOSIT_PENDING` | A deposit was detected but is not yet confirmed. On-ramp: fiat funding transition. Off-ramp: crypto confirmations accruing. | No |
| `AWAITING_AUTHORIZATION` | On-ramp only. Spike has prepared an outbound crypto withdrawal and needs partner approval before continuing. The response includes `authorizationContext` with the parameters to verify. | No |
| `PROCESSING` | Spike is executing work on the exchange — typically the trade leg, or the crypto withdrawal that follows an approved authorization. | No |
| `CANCELLATION_REQUESTED` | A `POST /v1/exchanges/cancel` request was accepted and the cancellation is in progress. | No |
| `COMPLETED` | The exchange finished successfully and any outbound transfer has been delivered. | Yes |
| `FAILED` | The exchange cannot be completed. See `failReason` and the aggregate `onramp.failed` / `offramp.failed` event for `failureContext`. Off-ramp `FAILED` exchanges that received funds typically need a partner-initiated refund — see [Refunds](refunds.md). | Yes |
| `CANCELLED` | The cancellation completed and the exchange is terminated. | Yes |

Refunds are tracked as a separate sub-resource on the exchange (`cryptoRefunds[]`), not as a status. A `FAILED` or `CANCELLED` off-ramp that received funds stays in that terminal status; the refund flow runs alongside it.

## On-ramp lifecycle

Use this state machine when fiat is being converted to crypto and delivered to an external wallet.

```mermaid
stateDiagram-v2
    direction LR
    [*] --> WAITING_DEPOSIT: POST /v1/exchanges
    WAITING_DEPOSIT --> DEPOSIT_PENDING: fiat detected, not confirmed
    WAITING_DEPOSIT --> PROCESSING: depositReference accepted
    DEPOSIT_PENDING --> PROCESSING: fiat confirmed
    WAITING_DEPOSIT --> CANCELLATION_REQUESTED: partner cancels
    DEPOSIT_PENDING --> CANCELLATION_REQUESTED: partner cancels
    CANCELLATION_REQUESTED --> CANCELLED: cancellation completes
    PROCESSING --> AWAITING_AUTHORIZATION: trade filled
    PROCESSING --> FAILED: trade rejected
    AWAITING_AUTHORIZATION --> PROCESSING: approve
    AWAITING_AUTHORIZATION --> FAILED: reject
    PROCESSING --> COMPLETED: withdrawal confirmed
    PROCESSING --> FAILED: withdrawal failed
    COMPLETED --> [*]
    CANCELLED --> [*]
    FAILED --> [*]
```

Key transitions:

- `POST /v1/exchanges` returns `200 OK`. A fresh partner-managed on-ramp normally returns `WAITING_DEPOSIT` while Spike processes the partner-supplied fiat `depositReference`. The partner must secure fiat before creating the exchange; this state does not mean Spike is waiting for the end user to pay.
- After the fiat reference is accepted, the exchange enters `PROCESSING`.
- After the trade fills, the exchange enters `AWAITING_AUTHORIZATION`. The partner verifies `authorizationContext` and calls either `POST /v1/exchanges/{id}/authorization/approve` or `POST /v1/exchanges/{id}/authorization/reject`.
- After approval, the exchange returns to `PROCESSING` while the crypto withdrawal is in flight. It reaches `COMPLETED` when the withdrawal is confirmed on-chain. The terminal aggregate event is `onramp.completed`.
- If the trade cannot fill, the exchange goes from `PROCESSING` to `FAILED`. If the withdrawal fails after a successful trade, the exchange also reaches `FAILED`. The terminal aggregate event in both cases is `onramp.failed` with `failureContext`.
- **Authorization-reject behavior**: when the partner calls `/authorization/reject`, the partner-visible status moves to `FAILED` and `onramp.failed` fires with `failureReason: AUTHORIZATION_REJECTED`. Note that the trading-leg `exchange.completed` event has already fired and is **not** retracted — the trade is final on Spike's side. The partner reverses fiat off-platform.
- **Cancellation**: only valid before the trading leg is created. After `PROCESSING` the partner cannot cancel.
- **`WAITING_DEPOSIT` does not currently expire**: an unfunded on-ramp can sit indefinitely. Partners must time out abandoned exchanges in their own bookkeeping and call `POST /v1/exchanges/cancel` to release the reservation. See [Failure handling](../operations/failure-handling.md) Section 2.

## Off-ramp lifecycle

Use this state machine when a crypto deposit is being converted to fiat and credited by the partner.

```mermaid
stateDiagram-v2
    direction LR
    [*] --> WAITING_DEPOSIT: deposit detected
    WAITING_DEPOSIT --> DEPOSIT_PENDING: confirmations accruing
    WAITING_DEPOSIT --> CANCELLATION_REQUESTED: partner cancels
    DEPOSIT_PENDING --> CANCELLATION_REQUESTED: partner cancels
    CANCELLATION_REQUESTED --> CANCELLED: cancellation completes
    DEPOSIT_PENDING --> PROCESSING: confirmations reached
    DEPOSIT_PENDING --> FAILED: deposit rejected
    PROCESSING --> COMPLETED: trade filled and fiat settled
    PROCESSING --> FAILED: trade rejected
    PROCESSING --> FAILED: fiat withdrawal failed
    COMPLETED --> [*]
    FAILED --> [*]
    CANCELLED --> [*]
```

Key transitions:

- An off-ramp exchange does not exist until a deposit is detected against a deposit address. While the deposit is being awaited, the deposit address is the integration object — not the exchange. Once a deposit is detected, Spike creates an exchange in `WAITING_DEPOSIT` and immediately advances it to `DEPOSIT_PENDING`.
- Each compliant deposit to a reusable address creates an independent exchange with its own `exchangeId`. See [Addresses](addresses.md) for the address model.
- When the required confirmations are reached, the exchange enters `PROCESSING` while Spike executes the trade and fiat-settlement legs automatically. There is no partner authorization step on off-ramp in this mode.
- The exchange reaches `COMPLETED` when the conversion fills and fiat is settled to the partner. The terminal aggregate event is `offramp.completed`.
- A deposit-phase rejection, trade-execution failure, or fiat-withdrawal failure all transition the exchange to `FAILED` with the terminal aggregate event `offramp.failed` carrying `failureContext`.
- **Refunds** on `FAILED` or `CANCELLED` off-ramps that received funds are a separate sub-resource. The exchange status does **not** become `REFUNDED` (no such status exists); instead `cryptoRefunds[]` populates and `refund.*` events fire. See [Refunds](refunds.md).

## Failure context

When `status == FAILED`, the response includes `failReason` (machine-readable) and `failDescription` (human-readable). The corresponding aggregate webhook event (`onramp.failed` / `offramp.failed`) carries a structured `failureContext` payload with the same information plus `failedPhase`. See [Events and Webhooks — Failure context](events-and-webhooks.md#failure-context) for the schema and recovery actions.

## Where to read state

Two sources, with the same semantics:

1. **Webhooks** (push). Every status transition emits at least one event. Aggregate terminal events (`onramp.completed`/`onramp.failed`/`offramp.completed`/`offramp.failed`) are the canonical "done" signal — each ramp emits exactly one in its lifetime. See [Events and Webhooks](events-and-webhooks.md).
2. **`GET /v1/exchanges/by-id`** (pull). The current state of any exchange. Use this as the authoritative fallback whenever a webhook is missed, late, or unclear.

For partner systems, treat webhook events as the trigger to act and `GET /v1/exchanges/by-id` as the source of truth.

## Common questions

**Does `exchange.completed` mean the user has been paid?**
Not by itself. On-ramp: `exchange.completed` is the trading-leg fill — the crypto has not yet left Spike. Wait for `onramp.completed` (or `withdraw.completed` followed by `status: COMPLETED`). Off-ramp: `exchange.completed` means the conversion is final and `amounts.to` is correct, but the partner-side fiat credit to the end user is the partner's responsibility.

**Can an exchange skip statuses?**
Yes. Failure paths can move an exchange directly to `FAILED` from `PROCESSING` or `AWAITING_AUTHORIZATION`. Always branch on the current status, not on an assumed sequence.

**Can a `COMPLETED` exchange become `FAILED` later?**
No. Terminal states are terminal. A post-completion operational issue is handled outside the exchange object — through refunds (off-ramp) or partner support (on-ramp).

**Is there a `REFUNDED` status?**
No. Refunds are a sub-resource (`cryptoRefunds[]`) that can populate on a `FAILED` or `CANCELLED` exchange. The parent status does not change. See [Refunds](refunds.md).
