Reference for crypto and AirTM withdrawals in the OFFER-HUB Orchestrator.
This page documents the withdrawal controller and DTOs in the OFFER-HUB Orchestrator: apps/api/src/modules/withdrawals/withdrawals.controller.ts, apps/api/src/modules/withdrawals/dto/create-withdrawal.dto.ts, and apps/api/src/modules/withdrawals/withdrawals.service.ts.
Every request uses Authorization: Bearer <api-key>. Write endpoints require the write scope; read endpoints require read.
destinationType | Provider path | Behaviour |
|---|---|---|
crypto | Stellar | Synchronous payment; the response is completed after the payment provider confirms it. |
bank | AirTM | Asynchronous payout; create first, then commit unless commit: true is supplied. |
airtm_balance | AirTM | Asynchronous payout; follows the same create/commit flow as bank. |
The statuses used by the service are WITHDRAWAL_CREATED, WITHDRAWAL_COMMITTED, WITHDRAWAL_PENDING, WITHDRAWAL_PENDING_USER_ACTION, WITHDRAWAL_COMPLETED, WITHDRAWAL_FAILED, and WITHDRAWAL_CANCELED.
/withdrawalsCreates a withdrawal. The request body is CreateWithdrawalDto:
| Field | Type | Required | Notes |
|---|---|---|---|
userId | string | yes | Internal user ID. |
amount | string | yes | Decimal string with exactly two decimal places, such as "5.00". |
currency | string | no | Defaults to USD. |
destinationType | "bank" | "crypto" | "airtm_balance" | yes | Selects the provider flow. |
destinationRef | string | yes | Bank/AirTM reference or Stellar destination address. |
commit | boolean | no | AirTM only; defaults to false. |
description | string | no | Optional description. |
The response contains id, amount, currency, status, destinationType, committed, and createdAt. Crypto withdrawals complete synchronously. AirTM withdrawals reserve funds and normally return WITHDRAWAL_CREATED; call commit next.
Errors include USER_NOT_FOUND (404), INSUFFICIENT_FUNDS (422), AIRTM_USER_NOT_LINKED (422), AIRTM_USER_INVALID (422), and validation errors (400).
/withdrawalsLists a user's withdrawals. The controller accepts only these query parameters:
| Parameter | Type | Required |
|---|---|---|
userId | string | yes |
limit | number | no |
cursor | string | no |
The response is { "data": WithdrawalResponse[], "hasMore": boolean, "nextCursor"?: string }.
/withdrawals/:idFetches one withdrawal. userId is required as a query parameter because the controller passes it to the service for ownership scoping.
An individual WithdrawalResponse includes id, userId, amount, fee?, currency, status, destinationType, destinationRef, airtmPayoutId?, failureReason?, createdAt, and updatedAt.
/withdrawals/:id/commitCommits an AirTM withdrawal created with commit: false. It accepts no body and requires userId in the query string.
This endpoint returns the updated WithdrawalResponse. It is not applicable to crypto withdrawals, which complete during creation. A non-committable state returns WITHDRAWAL_NOT_COMMITTABLE (409); a missing provider payout returns PROVIDER_ERROR (422).
/withdrawals/:id/refreshRefreshes an AirTM payout when a webhook may have been missed. It accepts no body and requires userId in the query string.
The response is the current WithdrawalResponse. Crypto withdrawals do not need provider refreshes.
The withdrawal controller does not declare an idempotency header or an idempotency DTO field. Do not describe Idempotency-Key as a withdrawal-specific guarantee until the controller and guard expose one. Clients should use their own request deduplication and treat the returned withdrawal ID as the operation identifier.
For provider-specific status and webhook events, see Webhooks, Balance Reference, and the upstream withdrawal endpoint source.