If your restaurant runs its own loyalty program, Karma can forward member identification, point accrual, and reward redemption to your URLs instead of Karma's native loyalty system. You implement six HTTPS endpoints; Karma calls them across a guest's loyalty lifecycle.
When this is used
A Karma operator enables the custom loyalty provider on a specific restaurant location. The restaurant (or its IT vendor) owns the loyalty database, and Karma becomes the point-of-sale client that asks your system who a member is, how many points they have, and what they can redeem.
Karma supports multiple loyalty provider types; this document covers the custom type only.
Lifecycle
- A guest identifies at the till (scans a card, enters a phone number). Karma calls lookup.
- A new guest signs up. Karma calls enroll to register them in your program.
- After a successful purchase, Karma calls award to credit points.
- The guest opens their loyalty screen. Karma calls account to show balance and tier.
- The guest browses rewards. Karma calls rewards to list what they can claim.
- The guest taps a reward. Karma calls redeem to spend their points.
What you implement
Six HTTPS endpoints of your choosing. The URLs do not need to share a hostname, a path prefix, or a deployment. You give Karma six absolute URLs when the integration is configured.
What you don't need
No inbound callbacks to Karma. No HMAC request signing (Karma does not sign outbound requests today). No Karma-specific SDK, library, or client certificate. Plain HTTPS, JSON in, JSON out, optional Basic Auth or custom header auth.
What you do need
Idempotency on award and redeem — if Karma retries a request with the same transactionId, your endpoint must not double-credit or double-spend points. See Idempotency below.
Authentication & security
HTTPS required. Every URL you give Karma must begin with https://. Plain http:// URLs are rejected by Karma's SSRF guard at request time. Karma also blocks URLs that resolve to private or loopback addresses (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fe80::/10, plus IPv4-mapped IPv6 forms of any of the above).
Auth modes (pick one per integration; applies to all six endpoints):
| Mode | Karma sends | You verify |
|---|---|---|
none | No Authorization header | Use only if your endpoints are IP-restricted |
basic | Authorization: Basic base64(username:password) | Compare decoded credential to your stored value |
headers | Any custom headers you configured (e.g. X-Api-Key) | Compare each header value to your stored secret |
Auth credentials (passwords, header values) are stored encrypted at rest (AES-256-CBC) and decrypted in memory only when a request is about to be sent. They are never written to logs.
No HMAC signing yet. Karma does not currently sign outbound requests. If your compliance requirements call for HMAC-SHA256 request signatures, raise it with Karma support.
Idempotency & retries
| Endpoint | Idempotency requirement | How |
|---|---|---|
lookup | Naturally — it's a read | Same identifier → same memberId |
enroll | Recommended | If a request comes in for a userId you already enrolled, return the existing memberId |
award | Required | Dedupe by transactionId — must not double-credit |
account | Naturally — it's a read | Same userId → current balance |
rewards | Naturally — it's a read | Same loyaltyProgramId → current catalog |
redeem | Required | Dedupe by transactionId — must not double-spend |
Karma retries on: HTTP 5xx, timeouts (5-second per-request budget), network errors (ECONNREFUSED, ECONNRESET, ENOTFOUND, ECONNABORTED).
Karma does NOT retry on 4xx — those are treated as definitive rejections, and the response body is dropped. Always signal application errors with HTTP 200 + ok: false + an error.code so Karma surfaces the right code to the UI.
Retry count: up to 2 retries (3 attempts total worst case), no backoff.
transactionId format: loy_<32 hex chars> (max length 100). A given transactionId is unique to a single operation — award and redeem get different IDs.
All money in cents, points as integers
Monetary values are integers in cents. Point values are whole integers. Always emit integer values; fractional values produce undefined behavior downstream. Round to the nearest whole point on the boundary.
Reference implementation
A minimal Node.js + Express server implementing all six endpoints lives in custom-integrations/loyalty-provider.md on GitHub.
Resolve a member by identifier
Karma calls this when a guest presents a loyalty card or enters a phone number/email at the till. Look the identifier up and return your stable member ID.
Path: Karma calls the absolute lookupUrl you configured per location.
Idempotency: naturally idempotent (read).
Resolve a member by identifier › Request Body
locationIdKarma location ID where the lookup is happening. Use to scope the search if your program is multi-tenant.
loyaltyProgramIdKarma loyalty program ID this lookup is for.
identifierThe value the guest presented. Format depends on identifierType.
identifierTypeTells you how to interpret identifier.
Resolve a member by identifier › Responses
Either a member or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, memberId | |
| type = object · requires: ok, error |
okmemberIdYour stable identifier for this member. Karma stores this and uses it in subsequent requests for the same guest. Treat as opaque on Karma's side; you choose the format and length.
userIdThe Karma user ID, if you have already linked this member to one. Omit if the member is anonymous in your system.
Register a new member
Karma calls this when a guest signs up to loyalty for the first time at a participating location.
Path: Karma calls the absolute enrollUrl you configured per location.
Idempotency: recommended. If a request arrives for a userId that is already enrolled, return the existing memberId rather than creating a duplicate.
Register a new member › Request Body
userIdKarma user ID of the guest being enrolled. Store this so you can link your member back to Karma.
loyaltyProgramIdKarma loyalty program ID being joined.
locationIdLocation where enrollment is happening. Useful for source attribution.
Register a new member › Responses
Either an enrolled member or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, memberId, pointsAwarded | |
| type = object · requires: ok, error |
okmemberIdYour stable identifier for the new member.
pointsAwardedSignup bonus points credited as part of enrollment. Return 0 if your program does not give a signup bonus.
Credit points after a purchase
Karma calls this once payment is confirmed for a transaction at a location with loyalty enabled.
Path: Karma calls the absolute awardUrl you configured per location.
Idempotency: required. Dedupe by transactionId.
Apply your own accrual rules to purchaseAmountCents. Use itemIds if your program awards bonus points for specific products.
Credit points after a purchase › Request Body
userIdKarma user ID who made the purchase.
loyaltyProgramIdKarma loyalty program ID to credit.
purchaseIdKarma purchase ID this award is for. Useful for reconciliation.
purchaseAmountCentsTotal amount paid, in cents. Apply your accrual rules to this value.
itemIdsInventory item IDs included in the purchase. Use these if your program awards bonus points for specific products.
transactionIdKarma-generated identifier in the format loy_<32 hex chars>. Your endpoint MUST dedupe by this value.
Credit points after a purchase › Responses
Either award details or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, pointsAwarded, newBalance +1 more | |
| type = object · requires: ok, error |
okpointsAwardedPoints credited for this purchase. May be 0 if the purchase didn't qualify.
newBalanceMember's total spendable point balance after this award.
tierChangedWhether this award caused the member to move up or down a tier. Karma uses this to refresh tier-related UI.
Get a member's current state
Karma calls this when a guest opens their loyalty screen, when a merchant looks them up at the till, or before showing a tier-gated reward.
Path: Karma calls the absolute accountUrl you configured per location.
Idempotency: naturally idempotent (read).
Get a member's current state › Request Body
userIdKarma user ID to fetch.
loyaltyProgramIdLoyalty program to fetch the account for.
Get a member's current state › Responses
Either the member's account or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, currentBalance, lifetimeEarned +2 more | |
| type = object · requires: ok, error |
okcurrentBalanceMember's spendable point balance right now.
lifetimeEarnedTotal points the member has ever earned. Return currentBalance if your program does not track lifetime separately.
tierNameHuman-readable tier name (e.g. "Silver"). Return null if the program has no tiers or the member has not reached one.
tierIdYour stable identifier for the tier. Return null whenever tierName is null.
List rewards a member can browse
Karma calls this when a guest opens the rewards catalog or when a merchant shows redemption options at the till.
Path: Karma calls the absolute rewardsUrl you configured per location.
Idempotency: naturally idempotent (read).
Return rewards in the order you want them displayed; Karma does not re-sort.
List rewards a member can browse › Request Body
loyaltyProgramIdThe loyalty program whose catalog to return.
List rewards a member can browse › Responses
Either the catalog or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, rewards | |
| type = object · requires: ok, error |
okZero or more reward objects. Return an empty array if no rewards are currently active.
Spend points on a reward
Karma calls this when a guest taps Redeem on a reward in the catalog or when a merchant rings one up at the till.
Path: Karma calls the absolute redeemUrl you configured per location.
Idempotency: required. Dedupe by transactionId. On a duplicate, return the same success body (including the same redemptionId and generatedCode) — do NOT re-spend points.
Spend points on a reward › Request Body
userIdKarma user ID redeeming the reward.
loyaltyProgramIdLoyalty program the reward belongs to.
rewardIdReward being redeemed (one of the IDs you returned from rewards).
transactionIdKarma-generated identifier in the format loy_<32 hex chars>. Your endpoint MUST dedupe by this value.
Spend points on a reward › Responses
Either redemption details or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, redemptionId, generatedCode +1 more | |
| type = object · requires: ok, error |
okredemptionIdYour stable identifier for this redemption. Karma stores this for receipts and refund flows.
generatedCodeRedemption code the guest can present to staff (printed on a receipt or shown in-app). Return null if the reward applies automatically and no code is needed.
newBalanceMember's point balance after this redemption.