Idempotency

Retry Wingspan V3 creates and money movement safely with the Idempotency-Key header, without creating duplicates or paying twice.

Retry requests that create resources or move money without duplicating the operation. A dropped connection or timeout does not tell you whether Wingspan completed the request. Use the Idempotency-Key header to retry safely.

How it works

Send a unique Idempotency-Key with a POST or PATCH. Wingspan stores the response for 24 hours. If you send the same request again with the same key, Wingspan returns the stored response instead of running the request a second time.

curl -X POST "https://api.wingspan.app/v3/payments/payables" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5d0b8f7e-2a41-4c3e-9f6d-8b1a2c3d4e5f" \
  -d '{
    "payeeId": "Rw5Nc2Hk8LqZ4tVp9XmBsa",
    "externalId": "INV-2026-0412",
    "currency": "USD",
    "dueDate": "2026-04-15",
    "lineItems": [
      { "description": "March site visits", "totalCost": 1250.00 }
    ]
  }'

The first response carries Idempotent-Replayed: false. If you send the identical request again within 24 hours, Wingspan returns the same status and body with Idempotent-Replayed: true and does not create a second payable.

When to send a key

We recommend sending an Idempotency-Key on every POST that creates a resource or moves money, and on any other POST or PATCH you might retry.

Some operations require the header, and their reference pages mark it required. Most are money movement, card, mandate, and webhook secret operations, plus create a batch and processing a batch. A missing required key returns 422 ValidationError (400 on purchases). On other operations, including most creates such as creating a payable or a payee, the header is optional. Sending it costs nothing and protects you from duplicates.

Wingspan honors the key on POST and PATCH only. On GET, DELETE, and other methods, Wingspan accepts the header and ignores it.

Choosing a key

  • Use 1 to 255 printable ASCII characters with no spaces. A UUID v4 works well.
  • Generate a new key for each distinct operation, such as each payable you mean to create.
  • Store the key with the operation in your system before you send the request, so a retry after a crash or restart uses the same key.
  • Don't reuse a key for a different operation, even after 24 hours.

What you get back

SituationResponse
First request with this keyThe request runs normally. Idempotent-Replayed: false.
Same key, same body, within 24 hoursThe stored response: same status, same body, and the stored Location, ETag, and Wingspan-Version headers. Idempotent-Replayed: true.
Same key, different body or different Wingspan-Version409 IdempotencyKeyConflict. Wingspan won't guess which request you meant.
Same key while the first request is still running409 IdempotencyKeyConflict with extensions.retryAfter (seconds). Wait that long and retry with the same key and body.
Same key after the first request returned 5xxThe request runs again. Server errors aren't stored, so the retry gets a fresh attempt.

Wingspan stores the first response with a status from 200 to 499 and replays it unchanged, including errors. If the first request returned 422 or 409, a retry with the same key returns the same 422 or 409. Use a new key when you send a corrected request.

Wingspan does not store requests rejected before they reach the operation, for example by authentication, Account scope, the version check, or rate limiting. Retry them with the same key.

Where a key applies

Wingspan scopes a key to the Account (or Person) that the request acts on, as well as the HTTP method and path. The same key sent to two different endpoints, or to the same endpoint with different X-Wingspan-Account values, counts as two separate keys. The request fingerprint includes the Wingspan-Version header, so changing it while using the same key returns 409 IdempotencyKeyConflict.

Limits of the guarantee

Stored-response replay is best-effort, not an exactly-once guarantee:

  • Wingspan does not store server errors. After a 5xx, the retry runs again. The first attempt may still have completed before the error. For creates, first look up the record by externalId with filter[externalId][eq]=.... An externalId provides a second line of defense: a duplicate returns 409 ResourceConflict instead of creating a second record.
  • Keys last 24 hours. Wingspan treats a later retry as a new request.
  • Wingspan does not store large requests. Request or response bodies over 1 MiB run without replay protection.
  • Storage can be unavailable. In rare cases, Wingspan cannot reach its idempotency store and runs the request without replay protection instead of failing it.

Some operations return one-time secrets, such as a new API key or webhook signing secret. Wingspan never stores those responses. Check the relevant reference page to learn what a repeated key returns.

What can go wrong

ResponseCauseFix
422 ValidationError on Idempotency-Key, InvalidFormatThe key is empty, longer than 255 characters, or contains spaces or non-ASCII charactersUse a UUID or another printable ASCII string
422 ValidationError on Idempotency-Key, InvalidContextYou sent the key without an authenticated caller or Account contextAuthenticate and send X-Wingspan-Account on Account-scoped endpoints if your token is not tied to an Account
422 ValidationError naming Idempotency-KeyThe operation requires the header. Some operations list it in errors[] with Required; others name it only in detailAdd an Idempotency-Key
400 BadRequest on a purchaseCreating a purchase requires the headerAdd an Idempotency-Key
409 IdempotencyKeyConflictSame key with a different body or Wingspan-Version, or the first request is still runningSee the table above

Related pages


Did this page help you?