Purchase events record points and stamps synchronously and return the member’s balance. Creating a discount or store-credit reward adds a merchant catalog item; a separate redemption records a member’s use of it. Provider gift-card payout remains unavailable.
Bearer-key auth, idempotent writes, HMAC-signed webhooks. Plain JSON over HTTPS, no SDK.
Purchase and redemption responses acknowledge ledger changes. Reward creation instead returns a catalog record, not a member balance or externally issued value. Rewardfinity never holds reward funds; catalog creation and recorded redemption do not prove delivery by an external system.
If you are looking for a payout rail that buys and mails a gift card on your behalf, this is not one, and there is no setting that turns it into one. Rewardfinity records the entitlement; the merchant funds the value. The vault path works because the codes are already yours — see how the gift-card vault works before designing around it.
Create API keys in Dashboard → Settings (owner keys have full access; staff keys are scanner-only). Keys are stored only as SHA-256 hashes, so a key is shown once and cannot be recovered — see Security. Send the key as a bearer token:
curl https://api.rewardfinity.com/v1/members \
-H "authorization: Bearer lp_sk_…" \
-H "content-type: application/json" \
-d '{"email":"customer@example.com","name":"Priya"}'
POST/v1/merchantsPOST/v1/membersGET/v1/members/:idPOST/v1/events/purchasePOST/v1/events/refundPOST/v1/rewardsPOST/v1/redemptionsPOST/v1/campaignsPOST/v1/campaigns/:id/codesPATCH/v1/campaigns/:idGET/v1/campaigns/:idPOST/v1/promosPOST/v1/wheels/:id/spinPOST/v1/members/importGET/v1/insightsGET/v1/automationsPublic (no auth): GET /p/:slug member page · POST /p/:slug/join self-enroll · POST /p/:slug/redeem member self-redemption · GET /c/:code claim page. Money is integer cents; points are integers; the ledger is append-only.
A till on a bad connection retries. That is the normal case, not the edge case, and it is why /v1/events/purchase is keyed on (source, externalId) rather than on arrival.
Work it through with a rate of 1 point per $1 — that is an illustrative setting, not a default, because the earn rate is yours to configure. A $40 sale posts once and the member's balance moves by 40. The till times out and posts the same order identifier twice more. The ledger still holds one entry for that order and the balance is still 40, not 120. Refund the order and /v1/events/refund claws back at the rate that applied when the purchase was recorded, so a rate change between sale and refund cannot leave the member up or down.
The same key protects redemptions: idempotencyKey on /v1/redemptions means a retried redeem spends the balance once. Balances come from an append-only integer ledger, so a correction is a new entry rather than an edit to an old one.
Set a webhook URL in Dashboard → Automations. Rewardfinity POSTs batched events — member.joined, points.earned, stamp.added, reward.redeemed, campaign.claimed, plus automation firings — signed with your secret:
// verify: hex(HMAC-SHA256(rawBody, secret)) === headers['x-torna-signature']
{ "type": "torna.outbox", "events": [
{ "id": "out_…", "kind": "points.earned",
"payload": { "email": "…", "pointsEarned": 25, "balance": 125 },
"createdAt": 1783380000000 } ] }
Automation kinds are lifecycle triggers (birthday, winback, welcome, anniversary, and expiry). Firings keep web push and signed webhook events, and queue each consented email, SMS, or WhatsApp leg through the first-party dispatcher. Provider submission occurs only when the corresponding operator-enabled provider path is configured. Failed webhook deliveries stay pending and are retried on the next flush rather than dropped; provider callbacks distinguish acceptance from delivery for direct channels.
Square connects natively (Dashboard → Integrations: paste your own access token; Rewardfinity registers its webhook and matches payments to members by email). Shopify uses signed store order and refund webhooks; Shopify POS remains an order-ingestion boundary until a POS extension is separately shipped and verified. Other POS systems (Clover, Lightspeed, Loyverse, Zettle…) drive the signed inbound endpoint POST /webhooks/in/:merchantId — directly or through Zapier/Make. Do not read the Shopify, Square, or generic webhook paths as one shared POS integration.
A fuller map of what connects and what does not is on integrations; the Shopify app is live and approved. What each plan includes is on pricing.
(source, externalId), so a retry after a timeout is recognised as the same order and does not stamp or award again. Redemptions take an idempotencyKey for the same reason. This matters more than it sounds: a till retrying on a weak connection is the ordinary case in a shop.hex(HMAC-SHA256(rawBody, secret)) with the secret from your dashboard and compare it to the x-torna-signature header. Use the raw request body, not a re-serialised object, or the bytes will differ and the comparison will fail. Failed deliveries stay pending and are retried on the next flush rather than dropped, so a brief outage on your side does not lose events. Note the asymmetry: the write is synchronous, the outbound webhook is queued, so do not treat your webhook as the confirmation that the write happened.