Skip to main content

Partner Rewards

For endpoint schemas, request/response examples, and error definitions, see the Rewards API reference.

The Partner Rewards API lets trusted backend integrations issue rewards to customers identified by phone number. Rewards are deposited into the customer's wallet when possible, or held as a pre-issued reward until the customer creates or activates a wallet.

Use this API when a partner system — such as a customer-support platform — needs to compensate or reward a customer on behalf of a merchant.

When to use

  • Issue a one-time reward to a customer after a support interaction, promotion, or goodwill gesture
  • Look up previously issued rewards for a phone number
  • Update or cancel a pre-issued reward before the customer claims it

All requests require your Client ID and Client Secret. See Merchant API Integration for authentication details.

Unified reward resource

Every issue, lookup, list, and update response uses the same JSON:API resource:

  • type is always Reward (immediate credits and pre-issued rewards share one shape)
  • kind is immediate or preIssued (informational only — do not branch behavior on this field alone)
  • status is always set: Credited for immediate wallet credits; Created, Claimed, Expired, or Cancelled for pre-issued rewards
  • canUpdate / canCancel indicate whether PATCH or DELETE is allowed for that reward
  • claimExpiresAt is the claim deadline for pre-issued rewards (ISO 8601 datetime); null for immediate wallet credits

Issue flow

When you call POST /api/v1/rewards, the API resolves the recipient's wallet state and chooses one of two outcomes:

flowchart TD issueReq["POST /api/v1/rewards"] --> idempotency{"Same idempotencyKey +<br/>identical request body?"} idempotency -->|yes| replay["Return prior result"] idempotency -->|no, different payload| conflict["409 IdempotencyConflict"] idempotency -->|new| walletCheck{"Active wallets for phone?"} walletCheck -->|exactly 1| adminOutcome["Credit wallet immediately<br/>kind: immediate<br/>status: Credited"] walletCheck -->|0, no blocking wallet| preIssuedOutcome["Create pre-issued reward<br/>kind: preIssued<br/>status: Created"] walletCheck -->|0, blocking wallet| notIssuable["409 WalletNotIssuable"] walletCheck -->|2+| multipleWallets["400 MultipleActiveWallets"] preIssuedOutcome --> preIssuedConflict["409 PreIssuedConflict if unclaimed pre-issued exists"]

Immediate credit (kind: immediate)

When the recipient has exactly one active wallet, the reward is credited immediately. The response has kind: immediate, status: Credited, walletId set, and canUpdate / canCancel are false.

Pre-issued reward (kind: preIssued)

When no active wallet exists and no blocking wallet is found, a pre-issued reward is created. The response has kind: preIssued, status: Created, walletId: null, and canUpdate / canCancel are true. The customer claims the reward when they sign up and create a wallet. Pre-issued rewards include claimExpiresAt — the deadline by which the customer must claim the reward (typically 90 days from creation). If unclaimed by that date, the reward moves to Expired.

Idempotency

Every issue request must include an idempotencyKey (1–255 characters). You do not send a hash — the API computes one server-side from the request body and stores it with the key.

Idempotency is scoped to (idempotencyKey, request body fingerprint). The fingerprint is a SHA-256 hash of the normalized phone number plus:

  • amount
  • message (optional)
  • reasonCode (optional)
  • costCenter (optional)
  • notificationEmail (optional)

Empty or whitespace-only optional strings are treated as absent.

  • Same key, identical body — the API returns the original result without creating a duplicate reward. Safe to retry on network failures by resending the exact same request.
  • Same key, different body — any change to the fields above (e.g. different amount or reasonCode) returns 409 IdempotencyConflict, even if the phone number is unchanged.

Generate a unique key per logical reward action (e.g. reward-{caseId}-{timestamp}). Use a new key when you intentionally change the reward details.

Example: issue to an active wallet

POST /api/v1/rewards

{
"data": {
"type": "IssueReward",
"attributes": {
"phoneNumber": "+15551234567",
"amount": 500,
"idempotencyKey": "reward-case-98765",
"reasonCode": "CX-COMPENSATION",
"message": "Thank you for your patience!"
}
}
}

Response (kind: immediate):

{
"data": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"type": "Reward",
"attributes": {
"kind": "immediate",
"amount": 500,
"walletId": "9f755746-13cb-4d0b-81f2-3b4f1b44f6d8",
"status": "Credited",
"canUpdate": false,
"canCancel": false,
"claimExpiresAt": null,
"message": "Thank you for your patience!",
"reasonCode": "CX-COMPENSATION",
"costCenter": null,
"notificationEmail": null
}
}
}

Example: issue as pre-issued reward

Same request shape. When no active wallet exists:

{
"data": {
"id": "4cf95060-dd01-42ac-9020-8ca42004920d",
"type": "Reward",
"attributes": {
"kind": "preIssued",
"amount": 500,
"walletId": null,
"status": "Created",
"canUpdate": true,
"canCancel": true,
"claimExpiresAt": "2026-09-15T12:00:00.000Z",
"message": "Thank you for your patience!",
"reasonCode": "CX-COMPENSATION",
"costCenter": null,
"notificationEmail": "agent@partner.example"
}
}
}

Lookup, update, and cancel

List requests use the platform JSON:API query shape: filter[<field>] for filters and page[offset] / page[limit] for pagination (same as users, payment intents, and external transactions).

ActionEndpointNotes
ListGET /api/v1/rewards?filter[phoneNumber]=...&page[offset]=0&page[limit]=10filter[phoneNumber] is required; optional filter[status] for pre-issued rewards
Get by IDGET /api/v1/rewards/{id}Returns { data: null } when not found
UpdatePATCH /api/v1/rewards/{id}Only when canUpdate is true; at least one field required
CancelDELETE /api/v1/rewards/{id}Only when canCancel is true; returns 204

Example list request:

GET /api/v1/rewards?filter[phoneNumber]=%2B15551234567&filter[status]=Created&page[offset]=0&page[limit]=10

Reward statuses: Credited (immediate), Created, Claimed, Expired, Cancelled.

Error handling

CodeHTTPMeaningWhat to do
InvalidRequest400Invalid phone, amount, email, or missing filter[phoneNumber] on listFix the request payload or query
WalletNotFound400Wallet not found for recipientVerify the phone number
MultipleActiveWallets400More than one active wallet matchesEscalate — data integrity issue
WalletNotIssuable409Wallet exists but cannot receive rewards (e.g. closing)Wait for wallet state to resolve or use a different channel
PreIssuedConflict409Unclaimed pre-issued reward already existsUpdate or cancel the existing pre-issued reward
IdempotencyConflict409Same idempotency key, different request bodyUse a new key, or resend the exact original body (phone, amount, and metadata)
NotFound404Reward not foundVerify the ID
InvalidState409Reward not updatable or cancellable in its current stateCheck canUpdate / canCancel before calling PATCH or DELETE