Verifying & Redeeming Claims
After a rewarded proposal closes - or you announce challenge winners - claims are distributed automatically. Vora gives you three ways to verify and mark them redeemed, scaled to the kind of integration you're running:
| Method | Best for | Auth |
|---|---|---|
| Verification page | In-person redemption (cafes, events, retail) | Admin JWT in the browser |
| Admin claims dashboard | Reviewing all claims for a proposal/challenge | Admin login |
| Server-to-server HMAC API | Automated redemption from your own storefront / commerce backend | Per-org HMAC shared secret |
This page walks through all three.
The Verification Page
Every reward email contains a public verification link:
https://voiceofthenewera.com/reward-verify.html?code={claim_code}&h={hash}
For is_local rewards the page is also reachable by scanning the inline QR code in the email.
What it shows
| Field | Description |
|---|---|
| Reward | The reward title |
| Scope | The proposal title or the idea challenge title |
| Status | pending (not yet redeemed), redeemed (claimed), or expired |
| Earned | The date the claim was created |
| Redeemed at | The timestamp when the claim was flipped to redeemed (null until then) |
The page deliberately does not display the voter's name or email. The claim code itself is the proof of vote; the verification response is bearer-only by design so anyone holding the code (the voter, a friend they gifted it to, your in-person staff) can validate without identity disclosure.
Who can mark a claim redeemed
The Mark as Redeemed button is only available when:
- The visitor is logged in to Vora in the same browser
- They are the owner of the organisation the claim belongs to
- The claim status is pending
Voters cannot mark their own claims redeemed. Random visitors with the code cannot mark it redeemed. This is what makes the bearer-only public verify safe.
In-person redemption flow
For front-line staff (baristas, door staff, retail associates):
- Voter approaches and shows the email - they can either let staff scan the QR or just read out the 8-character code
- Staff opens the verification page by scanning the QR, or by typing the short code into the storefront's redeem form, or by clicking the link in the email
- Staff confirms the reward title and status
- Staff clicks Mark as Redeemed (must be logged in as org owner)
- Status flips to
redeemed- the click is logged with the admin's user id - Staff delivers the reward
Server-to-Server Redemption (HMAC)
If you run an online storefront (Shopify, Next.js commerce, custom Lambda - anything that has a backend) you can have your own code mark Vora claims redeemed automatically, without an admin needing to be logged in. This is the recommended pattern for any reward that's redeemed online (pre-order unlocks, exclusive product access, discount codes that need to be one-time-use across both Vora and your storefront).
The auth model: per-organisation HMAC-SHA256 shared secret generated in your Vora settings, used by your backend to sign each request.
Set it up once
- Sign in to Vora as the space owner
- Go to Settings -> Rewards HMAC Secret (under the API Key panel)
- Click Generate - Vora creates a 64-character hex secret (256 bits of entropy)
- Copy the secret into your storefront's secret store (AWS Secrets Manager, Vercel env vars, etc.)
You can re-display the secret any time you're logged in as the owner. Clicking Regenerate rotates it; the previous value stops working on the next request.
The endpoint
POST https://api.voiceofthenewera.com/api/v1/rewards/verify/
Content-Type: application/json
X-Redeemer: <your-org-slug>-<any-id>
X-Timestamp: <unix-epoch-seconds>
X-Signature: <hex-hmac-sha256>
{"code": "<full-32-char-code>"}
The signature
signature = HMAC-SHA256(
key = your-rewards-hmac-secret,
msg = f"{timestamp}\n{raw_request_body}"
).hexdigest()
- The first segment of
X-Redeemeris your org slug - it tells Vora which secret to look up. The rest is free-form (use it as your own correlation id). X-Timestampmust be within +/-5 minutes of server time.- Use the raw request body bytes when signing - many libraries reorder JSON keys silently, which breaks the signature. Serialize once, sign that, send those exact bytes.
Response codes
| Status | Meaning |
|---|---|
200 |
Code is valid; if data.redeemable is true the claim was just flipped to redeemed |
400 |
Already redeemed (response includes redeemed_at) |
401 |
Bad signature, stale timestamp, or unknown redeemer prefix |
403 |
Signature valid but the claim belongs to a different org - cross-tenant access blocked |
404 |
Code does not exist |
Public verify (no auth)
For a read-only check - "is this code real and unused?" - without flipping the status, use GET:
GET /api/v1/rewards/verify/?code=<code>[&challenge=<slug>][&proposal=<uuid>]
No headers, no signature. Accepts either the full 32-char code or the 8-char short form (case-insensitive). The optional ?challenge= / ?proposal= parameters scope the lookup: a code from one campaign returns 404 against another campaign's query, preventing accidental cross-redemption.
Full integration guide
A complete brand-engineer guide with curl and Node.js examples lives in the source tree at docs/integrations/voting-code-external-gate.md. It covers operational hygiene (don't log signatures, cache the secret in memory, rotate safely) plus the full request/response shapes.
The Claims Dashboard
For each rewarded proposal you can open the admin claims view from the proposals list:
- See every claim with status, voter (admin-only), and redemption timestamp
- Filter by status (
pending/redeemed/expired) - Manually flip status when a customer reports they couldn't redeem
- Export to CSV for reconciliation against your commerce records
Related
- The Voter Experience - What voters see
- Reward Strategy Guide - Best practices for maximising redemption