Record loyalty activity. Manage reward redemption.
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.
Start in 60 seconds
Three steps take you from a new key to a recorded purchase. Keep the key in an environment variable or secret store, not in code.
- Create a key. Sign in and open Dashboard → Settings → Team & access. Copy the owner key when it is shown; it is stored only as a SHA-256 hash and cannot be shown again. An owner key also opens your dashboard, so treat it like a password and never paste it into an AI chat.
- Check it with
GET /v1/me. The response names the business, the key’s role and what it may do, so a wrong or revoked key fails here instead of halfway through a sale. - Record a purchase. Enroll the customer once with
POST /v1/members(idempotent by email), then send each sale with your own order identifier asexternalId. A purchase for an email that is not enrolled returns404 not_found.
Step 2 · curl
export REWARDFINITY_KEY="lp_sk_…" # shown once when you create it
curl https://api.rewardfinity.com/v1/me \
-H "authorization: Bearer $REWARDFINITY_KEY"
Response
{
"merchant": { "id": "mch_…", "name": "Corner Coffee", "slug": "corner-coffee-…", "currency": "CAD", "country": "CA" },
"key": { "id": "key_…", "role": "owner", "label": "Till integration" },
"permissions": [
"merchant:manage", "members:read", "members:write", "events:purchase",
"rewards:redeem", "program:manage", "campaigns:manage", "integrations:manage",
"reports:read"
],
"docs": "https://rewardfinity.com/developers",
"mcp": "https://api.rewardfinity.com/mcp"
}
A staff key returns the same shape with "role": "scanner" and only members:read, events:purchase, rewards:redeem; it can look members up and record sales, but enrolling needs an owner key. mcp is the MCP server URL, or null while agent access is switched off. Missing, unknown or revoked keys get 401.
Step 3 · curl
# Enroll the customer once: 201 when new, 200 when they already exist
curl https://api.rewardfinity.com/v1/members \
-H "authorization: Bearer $REWARDFINITY_KEY" \
-H "content-type: application/json" \
-d '{"email":"customer@example.com","name":"Priya"}'
# Record each sale with your own order identifier
curl https://api.rewardfinity.com/v1/events/purchase \
-H "authorization: Bearer $REWARDFINITY_KEY" \
-H "content-type: application/json" \
-d '{"memberEmail":"customer@example.com","amountCents":4000,"source":"till","externalId":"order-1042"}'
Steps 2 and 3 · JavaScript (fetch)
const API = 'https://api.rewardfinity.com/v1';
const headers = {
authorization: `Bearer ${process.env.REWARDFINITY_KEY}`,
'content-type': 'application/json',
};
async function call(method, path, body) {
const response = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
const result = await response.json();
if (!response.ok) throw new Error(`Rewardfinity ${response.status}: ${result.error}`);
return result;
}
const me = await call('GET', '/me'); // step 2: which business, which role
console.log(me.merchant.name, me.key.role, me.permissions);
await call('POST', '/members', { email: 'customer@example.com', name: 'Priya' }); // once per customer
const sale = await call('POST', '/events/purchase', {
memberEmail: 'customer@example.com', amountCents: 4000, source: 'till', externalId: 'order-1042',
});
console.log(sale); // { idempotent: false, pointsEarned: 40, referral: null, balance: 40 }
Steps 2 and 3 · Python (requests)
import os
import requests
API = "https://api.rewardfinity.com/v1"
session = requests.Session()
session.headers["authorization"] = f"Bearer {os.environ['REWARDFINITY_KEY']}"
def call(method, path, body=None):
response = session.request(method, API + path, json=body, timeout=10)
response.raise_for_status()
return response.json()
me = call("GET", "/me") # step 2: which business, which role
print(me["merchant"]["name"], me["key"]["role"], me["permissions"])
call("POST", "/members", {"email": "customer@example.com", "name": "Priya"}) # once per customer
sale = call("POST", "/events/purchase", {
"memberEmail": "customer@example.com", "amountCents": 4000, "source": "till", "externalId": "order-1042",
})
print(sale) # {'idempotent': False, 'pointsEarned': 40, 'referral': None, 'balance': 40}
The response carries the new balance. pointsEarned: 40 assumes an illustrative earn rate of 1 point per $1, which you configure. Send the same externalId again and the response says idempotent: true with the balance unchanged.
What arrives immediately, and what does not
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.
giftcard_payouts_unavailable until a connected provider can pay itIf 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.
Authentication
Create API keys in Dashboard → Settings → Team & access (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"}'
Errors are JSON with an error code: 401 unauthorized for a missing, unknown or revoked key (a paired staff device gets credential_revoked), 403 forbidden with the permission the key’s role lacks, 403 module_not_enabled with the module your plan does not include, and 400 validation with the schema issues.
Endpoints
GET/v1/mePOST/v1/membersGET/v1/members/:idPOST/v1/events/purchasePOST/v1/events/refundPOST/v1/rewardsPOST/v1/redemptionsPOST/v1/campaignscampaign_payouts_unavailable while campaign payouts are coming soon.POST/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.
Why the same purchase twice is safe
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.
POST /v1/events/purchaseis keyed on (source, externalId); a replay returns idempotent: true and awards nothing twice.POST /v1/events/refundis keyed on (source, refundExternalId).POST /v1/redemptionsis keyed on idempotencyKey; a retried redeem spends the balance once.POST /v1/membersis keyed on the normalised email; 201 when new, 200 when the member already exists.POST /v1/members/importis keyed on importId.
Webhooks
Set a webhook URL in Dashboard → Settings → Developer, where the signing secret is created too. 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.
POS connections
Square connects natively (Dashboard → Integrations: paste your own access token; Rewardfinity registers its webhook and matches payments to members by email). Shopify stores connect by installing the Rewardfinity app from the Shopify App Store; the app runs as its own workspace inside Shopify admin, with its own members and rewards, and is not linked to a web dashboard account. It 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.
Use Rewardfinity from AI agents (MCP)
Rewardfinity runs a remote MCP server, so an AI app that supports remote MCP connectors can read your workspace and prepare drafts for you to review. Add this URL as a connector:
https://api.rewardfinity.com/mcp
Sign in with OAuth. Your app opens Rewardfinity, you sign in as the owner and choose what it may do (read only, read and draft for your approval, or read and draft automatically) and for how long, up to 90 days. Apps without OAuth can send a personal token as Authorization: Bearer <token>; create one in Settings → AI & API, where it is shown once. Approving either needs an owner sign-in with a verified email, Google, Apple or Microsoft login.
Command-line clients
# Claude Code: add the server, then run /mcp to sign in
claude mcp add --transport http rewardfinity https://api.rewardfinity.com/mcp
# Codex
codex mcp add rewardfinity --url https://api.rewardfinity.com/mcp
codex mcp login rewardfinity
Editors: Add to Cursor · Add to VS Code. Each link asks your editor to add the server; follow its prompts to sign in.
Remote MCP connectors are offered by apps such as Claude, ChatGPT (developer mode), Cursor, VS Code and Codex. Rewardfinity has not yet confirmed any of these apps end to end against its MCP server, and availability depends on the app and your plan, so follow your app’s own connector instructions. Step-by-step setup for each app is in Settings → AI & API.
What a connected agent can do
- Read the connected workspace, its programs, aggregate insights, setup readiness and saved campaign drafts.
- Prepare private drafts of programs, messages, card designs, paused campaigns and campaign calendars for the owner to review.
What it cannot do
- Send messages, publish or activate a program or campaign, charge anyone or change billing.
- Change points, rewards or balances, record purchases or redeem rewards.
- See customer contact details or member-level records; insights are aggregate.
- Switch businesses: each grant covers one workspace, chosen at sign-in, and expires within 90 days.
Writes take two steps. rewardfinity_prepare_draft returns a preview and, unless you chose automatic drafting, a review link the owner opens to approve; rewardfinity_commit_draft then saves it as a private draft or paused campaign. Start with rewardfinity_whoami to confirm which workspace the connection belongs to.
Tokens are stored only as SHA-256 hashes, each completed tool call is recorded in an append-only audit, and you can revoke a connection at any time in Settings → AI & API. Prefer MCP over handing an agent an owner API key: MCP access is scoped and expires; an owner key is neither.
Claude API
Building your own agent? The Claude API’s MCP connector calls Rewardfinity’s tools from Anthropic’s side, using a personal token from Settings → AI & API as its authorization token. Model usage is billed to your own Anthropic account.
TypeScript (@anthropic-ai/sdk)
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic(); // your own Anthropic API key
const response = await client.beta.messages.create({
model: 'claude-opus-5-5',
max_tokens: 16000,
betas: ['mcp-client-2025-11-20'],
mcp_servers: [{
type: 'url',
url: 'https://api.rewardfinity.com/mcp',
name: 'rewardfinity',
authorization_token: process.env.REWARDFINITY_TOKEN, // personal token from Settings → AI & API
}],
tools: [{ type: 'mcp_toolset', mcp_server_name: 'rewardfinity' }],
messages: [{ role: 'user', content: 'Which loyalty programs do I have, and what should I review next?' }],
});
for (const block of response.content) if (block.type === 'text') console.log(block.text);
Python (anthropic)
import os
import anthropic
client = anthropic.Anthropic() # your own Anthropic API key
response = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
betas=["mcp-client-2025-11-20"],
mcp_servers=[{
"type": "url",
"url": "https://api.rewardfinity.com/mcp",
"name": "rewardfinity",
"authorization_token": os.environ["REWARDFINITY_TOKEN"], # personal token from Settings → AI & API
}],
tools=[{"type": "mcp_toolset", "mcp_server_name": "rewardfinity"}],
messages=[{"role": "user", "content": "Which loyalty programs do I have, and what should I review next?"}],
)
for block in response.content:
if block.type == "text":
print(block.text)
The MCP connector is a beta Claude API feature; check Anthropic’s MCP connector documentation for current availability. The token keeps the same limits as any connected agent: one workspace, reads and reviewable drafts only.
Questions developers ask
Does Rewardfinity have an API for instant reward delivery?
How long does a reward take to reach the member?
Can the API send a gift card automatically?
What happens if my POS sends the same purchase twice?
(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.How do I verify a Rewardfinity webhook signature?
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.