Understanding Payouts
Overview
Payouts are how you send value to your end users. You create a payout order containing one or more recipients; each recipient gets an email with a private redeem link and completes redemption on a hosted page. You track everything via the API and optional webhooks.
Three options exist, depending on how your campaign is configured:
- Gift card payouts — recipient picks a gift card.
- Crypto payouts — recipient receives crypto to their wallet.
- Hybrid payouts — recipient picks gift card or crypto at redeem time.
The API surface is the same for all three; the campaign determines which option(s) recipients see.
Prerequisites
- API token — Provided during onboarding. Sent as the
X-Api-Tokenheader on every request. - A configured campaign — Set up during onboarding. You'll receive its UUID.
- Sufficient balance — Top up before dispatching. Insufficient balance is rejected at order creation.
- An HTTPS
callback_url(optional, recommended) — For receiving signed webhooks.
Creating a Payout Order
All three payout types share the same endpoint:
POST /api/payout-orders
X-Api-Token: YOUR-API-KEY
Content-Type: application/json
{
"campaign_id": "<uuid>",
"wallet_currency": "GBP",
"recipients": [
{ "email": "[email protected]", "name": "Alice", "amount": 50, "currency": "GBP" }
],
"expires_in_days": 30,
"callback_url": "https://merchant.example.com/webhooks/gifq"
}Required fields
| Field | Notes |
|---|---|
campaign_id | UUID of the campaign you're dispatching against. |
wallet_currency | ISO currency code — the currency the order is charged in. |
recipients[].email | Where the redeem link is sent. |
recipients[].amount | Numeric face value the recipient can redeem. |
recipients[].currency | ISO currency code for the recipient's entitlement (may differ from wallet_currency). |
expires_in_days | Days until the recipient redeem link expires. |
Optional fields
| Field | Notes |
|---|---|
recipients[].name | Recipient display name. |
callback_url | HTTPS URL for webhook delivery. |
ledger_account_id | Required if the campaign supports crypto. See below. |
Response
202 Accepted with the payout order UUID. Save it to check status and reconcile.
Per payout type
| Campaign type | Extra fields | Constraint |
|---|---|---|
| Gift cards only | None | — |
| Crypto only | ledger_account_id | — |
| Hybrid (gift cards + crypto) | ledger_account_id | One recipient per order. |
The ledger_account_id identifies the account funding crypto sends — you'll receive yours during onboarding.
1. Gift Card Payouts
Use this when the campaign is configured for gift cards. Each recipient receives a redeem link and picks a gift card; delivery is handled end-to-end on a hosted page.
Creating the order
Standard payload — no extra fields:
{
"campaign_id": "<uuid>",
"wallet_currency": "GBP",
"recipients": [
{ "email": "[email protected]", "amount": 50, "currency": "GBP" },
{ "email": "[email protected]", "amount": 25, "currency": "EUR" }
],
"expires_in_days": 30
}Balance
The order total (including any FX fee) is reserved against your balance at dispatch. Insufficient balance is rejected with 422.
Reconciliation
Each gift card a recipient redeems appears as a row on GET /api/payouts — useful for matching invoices to redemptions. Recipients with leftover entitlement appear as partially_redeemed until they fully redeem or expire.
2. Crypto Payouts
Use this when the campaign is configured for crypto. Each recipient receives a redeem link and the funds are sent to their wallet; FX, network selection, and confirmation are all handled on the hosted page.
Creating the order
Same shape as gift cards plus one required field:
{
"campaign_id": "<uuid>",
"wallet_currency": "GBP",
"recipients": [
{ "email": "[email protected]", "amount": 75, "currency": "GBP" }
],
"expires_in_days": 30,
"callback_url": "https://merchant.example.com/webhooks/gifq",
"ledger_account_id": "<your-ledger-account-id>"
}ledger_account_id— Issued to you during onboarding. Identifies the account that funds the sends.
Balance
Crypto sends draw from your ledger account at redeem time, not at dispatch. If the account can't cover the order at that moment, the recipient is shown that option as unavailable until you top up.
Reconciliation
Subscribe to webhooks (payout.status_changed, payout.terminal_state) for state changes. For the authoritative view, GET /api/payout-orders/:id shows the current state for each recipient. A recipient is only considered final when their status reaches redeemed.
3. Hybrid Payouts
Use this when the campaign supports both methods and you want the recipient to choose at redeem time. Best for global rewards programs where preferences vary.
Creating the order
Same payload as crypto, plus one constraint:
- One recipient per order maximum. Hybrid orders are single-recipient by design.
{
"campaign_id": "<uuid>",
"wallet_currency": "GBP",
"recipients": [
{ "email": "[email protected]", "amount": 50, "currency": "GBP" }
],
"expires_in_days": 30,
"callback_url": "https://merchant.example.com/webhooks/gifq",
"ledger_account_id": "<your-ledger-account-id>"
}Balance
Reservation is deferred until the recipient picks. If their preferred option is not currently funded, the other option remains available — they can still proceed.
Reconciliation
On the order response, recipient.payout_type is null until the recipient picks, then resolves to gift_cards or crypto. From that point on, treat the order like the corresponding single-type payout for reconciliation.
Checking Status
Single order
GET /api/payout-orders/:id
X-Api-Token: YOUR-API-KEYReturns the order with its recipients, statuses, totals, and any FX fee applied.
Order statuses
| Status | Meaning |
|---|---|
pending | Created, awaiting dispatch. |
processing | Recipients notified; at least one still in flight. |
completed | All recipients reached a terminal state. |
failed | Order failed at dispatch (rare — usually validation). |
Recipient statuses
pending → sent → viewed → partially_redeemed / redeemed → expired
A partially_redeemed recipient has used some of their entitlement and may use the rest before expiry.
Listing
GET /api/payout-orders?status=completed&campaign_id=<uuid>&page=1&limit=20Filterable by status and campaign_id. Default limit is 10, max 20. Each response includes the standard pagination headers (current-page, page-items, total-count, total-pages) plus an RFC 5988 Link header with first / prev / next / last URLs for navigation.
Per-payout reconciliation (gift cards)
GET /api/payouts?payout_order_id=<uuid>&recipient[email][email protected]One row per redeemed gift card with brand, amount, currency, status, and recipient. Useful for finance reconciliation.
Webhooks
Pass callback_url on order creation and you'll receive signed POSTs as state changes occur. For the full event schema, signature verification details, and delivery semantics, see Payout Webhooks.
Events
| Event | Meaning |
|---|---|
payout.status_changed | A recipient's payout moved to a new non-terminal status. |
payout.terminal_state | A recipient's payout reached a terminal status — the body's status field tells you which. |
Headers
X-Gifq-Event— Event name (also in body as"event").X-Gifq-Signature— Signature for verifying the payload came from us.X-Gifq-Delivery— Fresh UUID per attempt — dedupe at-least-once delivery on it.
Sample body
{
"event": "payout.terminal_state",
"payout_order_uuid": "0e4a...",
"status": "completed"
}For the full response schema, see Common body fields.
Retries
Delivery retries on transport-level failures and 5xx responses with backoff. 4xx responses are not retried — return 2xx as soon as you've accepted the event.
Source of truth
Webhooks are best-effort. For anything financial, cross-check with GET /api/payout-orders/:id.
Important Details
Idempotency. Payout orders are server-deduplicated. If you retry a creation that you suspect succeeded, fetch first to confirm before resending.
Expiry. expires_in_days applies to each recipient's redeem link. Expired links cannot be redeemed; any reserved balance for an unredeemed gift-card portion is restored automatically.
FX. When wallet_currency differs from a recipient's currency, an FX conversion fee is applied and returned on the order response as fx_fee_amount. For crypto, FX is included in the recipient's quote.
Failed deliveries. If a gift-card delivery fails, the amount returns to the recipient's remaining balance so they can pick again. Crypto-side terminal failures leave the recipient redeemable to retry. Only completed finalizes a recipient.
Errors. Validation errors return 422 with field-level details and a request_id. Save the request_id if you need to contact support.
Listing & reconciliation. Use GET /api/payout-orders for the merchant view (one row per order you created) and GET /api/payouts for the per-card view. Webhooks tell you when state changes; the API tells you the current truth.
Updated 5 days ago