If your restaurant manages voucher codes in your own system, Karma can forward voucher operations to your URLs instead of Karma's native voucher store. You implement three HTTPS endpoints; Karma calls them as a guest applies a voucher code at checkout.
When this is used
A Karma operator enables the custom voucher provider on a specific restaurant location. The restaurant (or the restaurant's IT vendor) owns the voucher database, and Karma becomes the point-of-sale client that asks your system whether a code is valid and then tells you when to consume it.
Karma supports multiple voucher provider types; this document covers the custom type only.
Lifecycle
- The guest enters a voucher code at checkout. Karma calls your check endpoint to validate and reserve.
- The guest pays. Karma calls your commit endpoint to consume the voucher.
- The guest cancels or payment fails. Karma calls your reactivate endpoint to release the reservation.
What you implement
Three HTTPS endpoints of your choosing. The URLs do not need to share a hostname, a path prefix, or a deployment. You give Karma three 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 commit and reactivate — if Karma retries a request with the same transactionId, your endpoint must not double-consume the voucher. 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):
| 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. Authentication relies on HTTPS plus the Basic or header mode you configured. If your compliance requirements call for HMAC-SHA256 request signatures, raise it with Karma support.
Idempotency & retries
Karma automatically retries on:
- HTTP 5xx responses (500/502/503/504 etc.)
- Timeouts (5-second per-request budget)
- Network errors (
ECONNREFUSED,ECONNRESET,ENOTFOUND,ECONNABORTED)
Karma does not retry on 4xx — those are treated as definitive rejections.
Retry count: up to 2 retries (3 attempts total worst case), no backoff.
Every commit and reactivate request carries a transactionId in the body. Your endpoint MUST dedupe by this value — if you receive the same transactionId twice, return the same success body without making any additional change. A unique constraint on transactionId in your database is the simplest way to make this race-free.
transactionId format: res_<32 hex chars> (max length 100). The same transactionId is used for both commit and reactivate of a given reservation.
All money in cents
All monetary values are integers in cents. Never return fractional values. Always emit integer cents for discountAmountCents and amountCents; fractional values produce undefined behavior downstream.
Reference implementation
A minimal Node.js + Express server implementing all three endpoints lives in custom-integrations/voucher-provider.md on GitHub.
Check & reserve a voucher
Karma calls this when a guest enters a voucher code at checkout. Validate the code against the current cart and reserve its value. Return the discount amount Karma should apply.
Path: Karma calls the absolute checkUrl you configured per location — the path shown here is just an example.
Note on cart.totalCents: Karma sums priceCents * quantity across all items.
Note on discountAmountCents in the response: return a value no greater than cart.totalCents; the responsibility for keeping the discount within the cart total sits with your endpoint.
Note on expiresAt: if omitted, Karma applies a 2-hour default reservation window.
Note on providerRef: Karma echoes this opaque string back on commit/reactivate. Use it to correlate to your internal record.
Check & reserve a voucher › Request Body
codeThe voucher code the guest entered at checkout. Take this as the user-facing value; your system decides how to match (exact, canonicalized, case-insensitive, etc.).
Check & reserve a voucher › Responses
Either a successful reservation or an error envelope. Karma inspects ok.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok, voucherNr, discountAmountCents | |
| type = object · requires: ok, error |
okvoucherNrThe voucher identifier in your system. May equal the input code or a canonicalized form. Karma stores this as the reservation's voucher number.
discountAmountCentsHow much Karma should discount the cart, in cents. Return a value no greater than cart.totalCents.
expiresAtWhen this reservation expires. If omitted, Karma applies a 2-hour default.
providerRefOpaque string Karma echoes back on commit/reactivate. Use to correlate to your internal record.
Consume a reserved voucher
Karma calls this after the guest has successfully paid. Mark the voucher as used, decrement its remaining balance, or otherwise register the redemption.
Path: Karma calls the absolute commitUrl you configured per location.
Idempotency: required. Dedupe by transactionId.
amountCents: equals the discountAmountCents you returned from check.
Consume a reserved voucher › Request Body
amountCentsThe reserved amount Karma is committing. Equals the discountAmountCents you returned from check.
transactionIdKarma-generated reservation identifier in the format res_<32 hex chars>. The same value is used for both commit and reactivate of a given reservation. Your endpoint MUST dedupe by this value.
providerRefThe value you returned from the check response (if any). Echoed back so you can look up the original reservation by your own identifier.
Consume a reserved voucher › Responses
Either success or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok | |
| type = object · requires: ok, error |
okRelease a reservation
Karma calls this when the guest cancels payment, closes the checkout flow, or the reservation times out before a commit lands. Undo the reservation so the voucher can be used again.
Path: Karma calls the absolute reactivateUrl you configured per location.
Idempotency: required. Dedupe by transactionId.
Note on already-committed reservations: if commit already succeeded before the guest cancelled, you may either roll back the redemption (if your business rules allow) or respond with VOUCHER_ALREADY_COMMITTED. Karma logs the failure and proceeds; the guest's payment path is not affected.
Release a reservation › Request Body
amountCentsThe reserved amount Karma is committing. Equals the discountAmountCents you returned from check.
transactionIdKarma-generated reservation identifier in the format res_<32 hex chars>. The same value is used for both commit and reactivate of a given reservation. Your endpoint MUST dedupe by this value.
providerRefThe value you returned from the check response (if any). Echoed back so you can look up the original reservation by your own identifier.
Release a reservation › Responses
Either success or an error envelope.
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: ok | |
| type = object · requires: ok, error |
ok