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

  1. API token — Provided during onboarding. Sent as the X-Api-Token header on every request.
  2. A configured campaign — Set up during onboarding. You'll receive its UUID.
  3. Sufficient balance — Top up before dispatching. Insufficient balance is rejected at order creation.
  4. 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

FieldNotes
campaign_idUUID of the campaign you're dispatching against.
wallet_currencyISO currency code — the currency the order is charged in.
recipients[].emailWhere the redeem link is sent.
recipients[].amountNumeric face value the recipient can redeem.
recipients[].currencyISO currency code for the recipient's entitlement (may differ from wallet_currency).
expires_in_daysDays until the recipient redeem link expires.

Optional fields

FieldNotes
recipients[].nameRecipient display name.
callback_urlHTTPS URL for webhook delivery.
ledger_account_idRequired 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 typeExtra fieldsConstraint
Gift cards onlyNone
Crypto onlyledger_account_id
Hybrid (gift cards + crypto)ledger_account_idOne 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-KEY

Returns the order with its recipients, statuses, totals, and any FX fee applied.

Order statuses

StatusMeaning
pendingCreated, awaiting dispatch.
processingRecipients notified; at least one still in flight.
completedAll recipients reached a terminal state.
failedOrder 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=20

Filterable 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

EventMeaning
payout.status_changedA recipient's payout moved to a new non-terminal status.
payout.terminal_stateA 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.