> ## Documentation Index
> Fetch the complete documentation index at: https://public-openfx.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Withdrawals

> Settlement windows per rail, and how to track the time between submission and settlement: webhooks first, adaptive polling as a fallback.

Withdrawals don't complete instantly. The transition timing depends on the rail: seconds-to-minutes for stablecoins, hours-to-days for fiat. Use this page to know how long settlement typically takes, and how to poll without burning rate-limit budget waiting on a fiat wire.

## State machine

```mermaid theme={"theme":{"light":"poimandres","dark":"poimandres"}}
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Inter, system-ui, sans-serif','fontSize':'13px'}}}%%
stateDiagram-v2
  direction TB
  [*] --> PENDING: POST /v3/fx/withdrawals
  PENDING --> COMPLETED: Funds left OpenFX
  PENDING --> CANCELLED: Withdrawal doesn't go through
  COMPLETED --> [*]
  CANCELLED --> [*]

  classDef ok stroke-width:1px
  classDef bad stroke-width:1px
  classDef pending stroke-width:1px
  classDef hold stroke-width:1px
  classDef returned stroke-width:1px
  classDef neutral stroke-width:1px
  class PENDING pending
  class COMPLETED ok
  class CANCELLED bad
```

`Withdrawal.status` is a closed enum.

| Status | Meaning |
| - | - |
| <span class="ofx-status-pill ofx-status-pill--purple">PENDING</span> | Created. Funds have not left OpenFX yet |
| <span class="ofx-status-pill ofx-status-pill--green">COMPLETED</span> | Funds have left OpenFX. Final |
| <span class="ofx-status-pill ofx-status-pill--red">CANCELLED</span> | The withdrawal didn't go through. Final |

## Where money goes

The rail is bound to the verified withdrawal account. You don't pick it on the request.

* **Fiat withdrawals.** See [Settlement times](/v3/settlement-times) for the submission cut-off per currency and rail.
* **Stablecoin withdrawals.** See [Supported networks](/v3/supported-networks) for the per-stablecoin chain matrix.

## Fallback: polling pattern with adaptive backoff

<Tip>
  **Polling is a fallback.** Where possible, prefer receiving webhook events over polling for terminal status.
</Tip>

If you can't yet receive webhooks, don't poll every 5 seconds for fiat; you'll burn rate-limit budget waiting on a rail whose settlement spans banking hours.

This sample waits about 20 minutes in total (the delays sum to 1,215 seconds), which suits stablecoin withdrawals and fiat withdrawals that settle within the same banking window. A fiat withdrawal can take hours to days, so when the schedule runs out, stop polling and rely on webhooks or a later check instead of treating the error as a failure.

```javascript theme={"theme":{"light":"poimandres","dark":"poimandres"}}
async function waitForWithdrawal(withdrawalId, signRequest) {
  const delays = [
    5_000, 10_000, 20_000, 40_000, 80_000, 160_000, 300_000, 300_000, 300_000,
  ];
  const url = `https://api.openfx.com/v3/fx/withdrawals/${withdrawalId}`;
  for (let i = 0; i <= delays.length; i++) {
    const signed = signRequest({ method: "GET", url });
    const res = await fetch(url, { headers: signed.headers });
    const w = await res.json();
    if (w.data?.status === "COMPLETED") return w;
    if (w.data?.status === "CANCELLED") {
      throw new Error(`Withdrawal ${withdrawalId} was CANCELLED.`);
    }
    // Sleep only between polls, never after the last one.
    if (i < delays.length) {
      await new Promise((r) => setTimeout(r, delays[i]));
    }
  }
  throw new Error(
    "Withdrawal not COMPLETED after backoff exhausted; consider webhook delivery.",
  );
}
```

## When a withdrawal is stuck

### Step 1: read the trace ID

Every response carries `X-Trace-Id`; include it if you escalate to OpenFX support.

### Step 2: re-fetch the resource

```bash theme={"theme":{"light":"poimandres","dark":"poimandres"}}
curl https://api.openfx.com/v3/fx/withdrawals/<uuid> \
  -H "Authorization: Bearer $OPENFX_JWT" \
  -H "X-Request-Signature: $OPENFX_SIGNATURE"
```

### Step 3: check the estimated completion time

Read `estimatedCompletionAt` on the withdrawal. It's the time the withdrawal is expected to settle, so a withdrawal that is still <span class="ofx-enum">PENDING</span> before that time isn't stuck. It's `null` when there's no estimate yet.

### Step 4: match the state against the rail clock

Not-yet-settled past the rail cut-off is the most common cause of "stuck." Common cases:

* **Fiat USD via Fedwire:** submissions after 4:30 PM ET batch the next business day.
* **Fiat EUR via SEPA:** submissions after 12:30 PM London time batch the next business day.
* **Stablecoin on Ethereum:** network congestion can delay confirmation 10+ minutes.

## Common mistakes

* **Fixed-interval polling at 5s.** Fine for the first minute; wasteful for a fiat withdrawal that spans banking hours.
* **Polling without an upper bound.** Always have a "give up and alert" condition.

## What's next

<CardGroup cols={2}>
  <Card title="Settlement models" icon="https://public-assets.openfx.com/docs/icons/settlement-models.svg" href="/v3/settlement-models">
    The full deposit / balance / withdrawal model.
  </Card>

  <Card title="Errors" icon="https://public-assets.openfx.com/docs/icons/errors.svg" href="/v3/errors">
    Recovery patterns for rejected withdrawals.
  </Card>

  <Card title="Idempotency" icon="https://public-assets.openfx.com/docs/icons/idempotency.svg" href="/v3/idempotency">
    7-day TTL on withdrawal keys.
  </Card>

  <Card title="Withdrawal webhooks" icon="https://public-assets.openfx.com/docshttps://public-assets.openfx.com/docs/icons/withdrawals.svg" href="/v3/webhooks/withdrawals/withdrawal-completed">
    Receive a push notification when a withdrawal completes instead of polling.
  </Card>
</CardGroup>
