Model C
Your codes stay in your system. At checkout Brizz calls an endpoint you host; your answer decides whether the discount applies. This is the only model where you run a service.
You build an HTTPS verification endpoint
Direction of travel
In Models A and B you call Brizz. In Model C Brizz calls you — you are the server. Same request shape, very different operational commitment: this endpoint sits inside a live checkout with a customer waiting on it.
Machine-readable contract
The endpoint you implement is published as an OpenAPI provider contract at /coupon-integration/partner-verification-provider-v1.yaml. Redemption reads + webhooks use the Partner API contract.
How a coupon travels from your app to a Brizz checkout and back — read left to right. The one box on your side during checkout is what makes Model C different; everything after it is standard Brizz machinery.
code.redeemed webhook — detailed under After approval below.Three secrets, issued by Brizz
With signing enabled on both channels — the setup we recommend — Brizz issues you three credentials, each generated on the Brizz side, revealed once, and sent out of band: the API key you present on the verification call, the verification HMAC secret, and the webhook HMAC secret. The two HMAC secrets are distinct — verify each channel against its own. You may instead supply the two verification-endpoint secrets yourself; the webhook secret is always Brizz-issued.
Brizz decides whether to call you from the code alone, before any outbound request. A code is uppercased and trimmed, then it must be 4–32 characters of A–Z, 0–9 and hyphen. Brizz then routes it to you by one of two rules you choose: a prefix token matching [A-Z0-9]{2,16}- (the trailing hyphen is part of the token), or a prefixless mapping tied to specific events — in which case a bare code with no prefix (e.g. SUMMER10) still routes to you.
A code over 32 characters never reaches you
A matched code is stored as string(32), so anything longer — a raw UUID is 36 — is rejected locally with zero calls to you, and can never apply a discount. A code that matches no configured route is rejected locally too. If your codes can exceed 32 characters, shorten or re-issue them before routing to Brizz.
Content-Type: application/json
User-Agent: Brizz-Verification/1.0
Authorization: Bearer <secret> # or: X-API-Key: <secret>
# if HMAC signing is enabled:
X-Brizz-Timestamp: 1784654651
X-Brizz-Request-Id: <uuid>
X-Brizz-Signature: v1=<hex>
{ "code": "PARTNER-AB12", "campaign_uuid": "…", "request_id": "<uuid>" }No customer PII leaves Brizz. You issued the code to a specific person, so the code is already your user reference — you can answer “is this valid, and is this user new to us” from your own records alone.
Keep request_id in your logs. It is the handle Brizz support uses to correlate one specific call with your side, and it is in the body of every request — the X-Brizz-Request-Id header carries the same value but is only sent when signing is enabled.
signed_payload = "{timestamp}.{request_id}.{raw_body}"
expected = HMAC_SHA256(your_hmac_secret, signed_payload)Use the raw body exactly as received — do not re-serialise the JSON. Compare in constant time against any v1= candidate (multiple appear only during rotation), and reject if |now − timestamp| > 300 s.
Exactly HTTP 200, application/json, at most 64 KiB. Anything else — another status, a non-JSON body, a timeout — is treated as unavailable: no discount, and checkout continues normally.
# approve
{ "approved": true, "authorization_ref": "ptr-9f2c…", "reason": null }
# deny
{ "approved": false, "reason": "ALREADY_USED" }approved — Required, strict JSON boolean. A truthy string or number is rejected as malformed.authorization_ref — Optional on approval. Opaque, ≤128 characters, no PII. Over-length keeps the approval but drops the reference — we never truncate it, because a truncated identifier can collide.reason — Optional, ≤256 chars, resolved against a CLOSED vocabulary: INVALID · EXPIRED · ALREADY_USED · NOT_ELIGIBLE · REVOKED. Matching is forgiving about case, whitespace, hyphens and dots, so “already used” and “Already-Used” both land on ALREADY_USED. Never shown to the customer.money fields — discount, amount, price, value, discount_eur and discount_cents are read for a bounded metric and then discarded — never persisted, and never used to price anything. Brizz prices from the campaign's own budget config, under a lock. Your endpoint answers yes or no; it cannot set the discount.A reason outside the list is hashed, not stored
Anything that does not resolve to one of the five is replaced with OTHER_<12 hex>, a keyed digest of the string you sent. Your raw text never survives into results, cache, logs or metrics — so a free-text reason cannot carry customer data into Brizz through this field, but you also lose the ability to read it back. Use the vocabulary.
POST /verification:
1. authenticate
if header(Authorization) != "Bearer " + OUR_SECRET → 401 (constant-time)
2. verify signature, if you enabled HMAC
payload = timestamp + "." + request_id + "." + raw_body
if no v1= candidate matches HMAC_SHA256(secret, payload) → 401
if abs(now - timestamp) > 300 → 401
3. look the code up in YOUR store
coupon = find(body.code)
if !coupon → 200 { approved: false, reason: "INVALID" }
if coupon.used → 200 { approved: false, reason: "ALREADY_USED" }
if coupon.expired → 200 { approved: false, reason: "EXPIRED" }
4. approve
return 200 { approved: true, authorization_ref: coupon.id }Do not mark the code used here
A verification call is not a redemption — the customer may still abandon checkout. If you burn the code on verify, an abandoned basket consumes it permanently. Hold it as a reservation and settle only when you see the real redemption — the webhook and the report carry both the code and the authorization_ref you returned, so you can match on either.
Every failure resolves the same way for the customer: no discount, full price, checkout continues. What changes is what Brizz does next.
Five failures inside a 300-second window trip the breaker; any success in between clears the count. Once tripped, Brizz stops calling you for a 60-second cooldown and denies locally. After the cooldown exactly one request probes you — everything else keeps failing closed until that probe resolves, and a failed probe re-opens the breaker immediately.
What the single probe buys you
Recovering from an outage will never hit you with the backlog at once. You get one request, and normal traffic resumes only if it succeeds. The breaker is keyed per endpoint, so one partner's outage never affects another's.
We never receive or hold your code universe — we only learn a code when a customer actually types it. On approval Brizz materialises that single code as one row, marked as originating from real-time verification, carrying your authorization_ref. That row is the only double-spend defence there is: without it there is nothing to lock, so two concurrent checkouts could each spend the same code. It is also what lets the existing budget-cap, per-user-limit, webhook and reporting machinery treat your codes exactly like any other.
Verification itself creates no coupon, reservation, booking or payment record. Brizz may write bounded operational telemetry — a keyed hash of the code plus an outcome category, never the raw code or your reason text. The short-lived approval cache is likewise keyed by that hash, never your raw code, and any change to your endpoint configuration discards cached approvals immediately rather than waiting for them to expire.
Your endpoint is called while a customer is mid-checkout, so an approval is a reserve-time answer — not proof the code was spent. The customer may still not pay. You learn the real outcome after checkout, and only for the codes that are actually redeemed.
On a completed booking — you get a webhook
Because Brizz materialised the code on approval, a real redemption flows through the same machinery as Models A and B: Brizz pushes a signed code.redeemed event to your webhook endpoint — at-least-once, deduped on event id, the identical contract documented on Model B. It is your real-time “it was used” signal, and it only fires if you have configured a webhook.
If it is never redeemed — silence
An abandoned checkout, an expired code, a code verified but never bought — none of these send anything. A verification is not a purchase, and code.redeemed is the only event there is. Reconcile the redemptions you do see against the approvals you granted — the webhook and report carry both the code and your authorization_ref.
A later refund or cancellation is not pushed either — it appears in the reconciliation report alongside the redemptions, which stays the source of truth for what you are owed.
Everything above, in one screen — the checklist to build and verify your endpoint against before you go live.
Your endpoint
Authorization: Bearer or X-API-Key. Brizz sends you the secret.{timestamp}.{request_id}.{raw_body} — reject if |now − timestamp| > 300 s, and accept any v1= during rotation.Your reply — every time
200 + application/json + a strict boolean approved (a string or number is rejected as malformed).reason from the closed vocabulary; keep authorization_ref ≤128 chars and free of PII.After approval & go-live
code.redeemed webhook — authenticate by signature alone, dedupe on the event id, return 2xx. Refunds appear in the report, not a webhook.approved: false still passes.Brizz runs a synthetic Test connection against your endpoint. It uses the same transport, the same request body and the same signing as production, so it exercises the full wire — URL, TLS, auth, HMAC and response shape — with the code BRIZZ-CONN-TEST, prefixed with your route's prefix when one is configured.
That code cannot match anything on the Brizz side, so answering approved: false still passes. Your approve/deny answer is discarded entirely; only reachability and protocol shape are reported.
Safe to run against production
Test connection does not go through the live verification path. It writes no preview cache and cannot move your circuit breaker, so a failing test can never affect real checkouts and a passing one can never seed a live approval. It mints no code, creates no reservation or booking, moves no budget and takes no payment.
Brizz uses cookies to improve your experience and measure site usage. You can manage your preferences at any time.