Brizz

Model C

Real-time verification

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.

Flow

PARTNERBRIZZEND USERAHEAD OF TIMEAT CHECKOUT — THE CUSTOMER IS WAITINGYou issue a codein your own appGets code from youEnters at checkoutCode unknownrouted to youYOU VERIFY ITapprove or denyDiscount appliedPays lesslive call · a customer is waiting
One box sits in the partner lane to the right of the divider, and it is the only such box in the entire guide. That is the difference between Model C and the other two: your service is inside the checkout path, so its latency and availability become the customer's experience.

What you need from your side

  • RequiredA public HTTPS endpoint with valid TLSPlain http:// is rejected before a connection is opened, as are private, loopback, link-local, CGNAT, cloud-metadata and multicast addresses — IPv4 and IPv6 alike. A hostname that resolves into one of those ranges is rejected too.
  • RequiredA bearer credentialSent as Authorization: Bearer <secret>, or as X-API-Key: <secret> if you prefer — those are the only two header names accepted. Brizz can generate the secret and reveal it once, or you can supply your own.
  • RequiredAn implementation of the contract belowTypically a thin lookup against your own coupon store — see the minimal implementation further down.
  • RequiredA routing ruleA code prefix such as PARTNER-, or a prefixless mapping tied to specific events, so Brizz knows which codes to route to you.
  • RequiredA latency commitmentDefault timeout 1500 ms, configurable between 100 and 5000 ms — a value outside that range is clamped into it, not rejected. Brizz also applies its own ceilings of 5 s to connect and 10 s in total. Slower than the timeout is treated as unavailable.
  • OptionalAn HMAC signing secretStrongly recommended: it lets you verify a request genuinely came from Brizz rather than trusting the bearer token alone.

The request Brizz sends

POST /your/verification/path
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.

Verifying the signature

HMAC construction
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.

The response you return

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.

Response shape
# approve
{ "approved": true, "authorization_ref": "ptr-9f2c…", "reason": null }

# deny
{ "approved": false, "reason": "ALREADY_USED" }
  • approvedRequired, strict JSON boolean. A truthy string or number is rejected as malformed.
  • authorization_refOptional 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.
  • reasonOptional, ≤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 fieldsdiscount, 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.

A minimal implementation

Pseudocode
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. Mark it used when you see the redemption in reconciliation, or treat your authorization_ref as a reservation and settle later.

Failure behaviour, and the circuit breaker

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.

What Brizz stores

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.

Before that, during verification itself, nothing is written to the database at all, and the short-lived approval cache is keyed by a keyed hash — never by your raw code. Any change to your endpoint configuration discards cached approvals immediately rather than waiting for them to expire.

Before go-live

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-CONNECTION-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.