Skip to content

Create a customer's reward, with its opening entry

Request

A reward cannot be opened empty — the discount is created from the balance, and a zero balance produces an already-expired code — so the reward is opened together with its first entry. The customer must already exist; an unknown customer returns 404. Create them with POST /customers and retry.

Retrying is safe. The opening entry is de-duplicated on shop_transaction_id exactly like any other entry, so a retry after a timeout returns 200 with the existing reward and the entry applied once, never twice. A customer who already holds a reward gets that same 200 rather than a second reward. Note this differs from POST /rewards/{shopRewardId}/transactions, which returns 409 for an already-recorded entry.

Security
api_key
Path
shopCustomerIdstringrequired

The customer's shop_customer_id.

Headers
Content-Typestringrequired

Content-Type must be application/json

Example:application/json
Acceptstringrequired

Accept must be application/json

Example:application/json
Bodyapplication/jsonrequired
shop_transaction_idstringrequired

The entry's ID in your platform. Unique and stable: it addresses the entry and de-duplicates writes, the same way shop_order_id does for orders. It is what makes a forwarder safe to retry. Avoid / and . — the value travels in a URL path segment.

Example:"inveterate:1a2b3c"
amountobject(Money)required

Positive to credit; negative to reverse credit previously issued. Currency must match the reward's currency. Onward derives the presentment and USD amounts itself — do not send them.

issues_atstring, (date-time)

A future timestamp holds the entry as pending issuance until then, after which it becomes spendable. Omit for immediately spendable credit — a past or present value is rejected rather than treated as immediate, so there is exactly one way to express "spendable now". The field is cleared once the entry issues, so a matured entry reports null.

source_detailsobject(SourceDetails)required

Where the entry came from. name is a stable lowercase slug assigned when your integration is onboarded, and it is the only key Onward reads — it attributes the entry to the platform that sent it, since the API key resolves to a shop rather than to a caller. Every other key belongs to you: Onward stores it verbatim, surfaces it for support, and never interprets it. Keep it a flat object of string values with a small number of keys. There is deliberately no Onward vocabulary for why credit was earned, so your own reason travels here.

Example:
{ "name": "inveterate", "reason": "SIGNUP_STORE_CREDIT" }
curl -i -X POST \
  'https://app.useonward.com/api/v1/customers/{shopCustomerId}/rewards' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY_HERE' \
  -d '{
    "shop_transaction_id": "inveterate:1a2b3c",
    "amount": {
      "cents": 1299,
      "currency": "USD"
    },
    "issues_at": "2019-08-24T14:15:22Z",
    "source_details": {
      "name": "inveterate",
      "reason": "SIGNUP_STORE_CREDIT"
    }
  }'

Responses

The customer already held a reward, or this entry was already recorded.

Bodyapplication/json
shop_reward_idstring or nullrequired

The ID of the discount this reward is tied to. Store it — later calls address the reward by it. Can be null when the discount has not been created yet or has gone missing: the balance is written before the discount is synced, so a platform outage leaves the reward addressable only once the sync retries. Re-read GET /customers/{shopCustomerId} to pick it up. Also null when your shop has no redemption mechanism, since there is nothing to address the reward by.

Example:"1234567890"
balanceobject(Money)required

Spendable balance.

pending_issuance_balanceobject(Money)required

Credit that is scheduled but not yet spendable, reported separately from the spendable balance.

lifetime_issuedobject(Money)

Credit issued, net of reversals — not a gross "total ever earned". A reversal is stored as a negative entry in the same bucket, so it reduces this figure, and the identity lifetime_issued = balance + lifetime_used always holds. Do not render it as lifetime earnings.

lifetime_usedobject(Money)

Credit redeemed against the discount code.

lifetime_refundedobject(Money)

Always zero. Onward does not currently write this bucket — refunds are modelled as reversal ledger entries, not as a lifetime total. Do not build on this field.

redemption_detailsobject or null(RedemptionDetails)required

How the customer can spend this balance, for example with a discount code. null when there is nothing to redeem: the balance is zero, or the shop has no way to redeem it. More types may be added later, so ignore any type you don't recognize.

balance_updated_atstring, (date-time)
Response
{ "shop_reward_id": "1234567890", "balance": { "cents": 1299, "currency": "USD" }, "pending_issuance_balance": { "cents": 1299, "currency": "USD" }, "lifetime_issued": { "cents": 1299, "currency": "USD" }, "lifetime_used": { "cents": 1299, "currency": "USD" }, "lifetime_refunded": { "cents": 1299, "currency": "USD" }, "redemption_details": { "name": "shop", "type": "discount_code", "code": "ONWARD-e77c3802fd6f9d7d6daae8e286a80f11", "url": "https://shop.example.com/discount/ONWARD-e77c3802fd6f9d7d6daae8e286a80f11?ref=onward" }, "balance_updated_at": "2019-08-24T14:15:22Z" }