Appearance
ID Verification
Customers upload up to four photos of a government ID; an admin reviews each request and either approves (marks the user as 18+ and verified) or rejects with a reason. Approved users can withdraw cash and claim prizes; rejected users see the reason and can resubmit. Photos auto-delete from Cloudflare three days after the decision.
Where customers see it
- Account → Profile has a panel for uploading or checking status. Drag-and-drop on desktop, native picker on touch devices. Up to four photos, JPG/PNG/HEIC, 10MB each.
- After submission the panel shows Pending review with thumbnails until an admin decides.
- On approval the panel shows ID Verified.
- On rejection the panel shows the rejection reason and an admin note (if provided), with a "Submit again" button.
Reviewing requests
Open the queue at /admin/id-verifications. The default filter is Pending, oldest first.
Opening a row prompts you for a TOTP code (step-up). The image URLs returned by the server expire after five minutes — if you spend longer than that on a single request and a thumbnail breaks, click it to refetch (you'll be prompted again).
Approving prompts you for a confirmation, then a fresh TOTP code — both the view and the approval are recorded in the audit log. Rejecting submits directly once you've picked a reason and (optionally) added a note: the on-entry step-up already covers the audit trail, and rejections are recoverable (the user can resubmit).
Approve checklist
Before approving, confirm all of:
- The document is in date and looks genuine.
- The name on the document matches the user's account name.
- The date of birth on the document confirms the user is 18 or older.
Approving flips the user's idVerified flag to true in the same transaction as the decision. The flag covers both 18+ and identity confirmed.
Reject reasons
Pick the reason that best matches what's wrong. The wording shown to the user is locked.
| Reason | Customer sees |
|---|---|
UNCLEAR_IMAGE | The photo is too blurry, dark, or partially obscured. |
EXPIRED_DOCUMENT | The document has expired or is no longer valid. |
NAME_MISMATCH | The name on the document doesn't match your account name. |
AGE_INSUFFICIENT | The document indicates the holder is under 18. |
OTHER | Please contact support for details. |
When you pick OTHER you must include an admin note (≤500 characters). Notes are visible to the user as plain text, so keep them factual and kind. Notes are optional for every other reason.
Settings
Two operator toggles live under Settings → Identity Verification. Both default to off.
- Require ID verification before withdrawals — when on, a customer can't request a cash withdrawal until they've been approved.
- Require ID verification before prize claims — when on, a customer can't take a prize, take cash, or take credit on a winning ticket until approved.
Turning a toggle on does not retroactively block in-flight requests; it gates new submissions. The first place a customer sees the gate is the relevant page (Wallet for withdrawals, Prizes for prize claims) — a banner explains what's needed and links them to the upload panel.
Manual override (legacy and edge cases)
The user detail panel (/admin/users/[id]) exposes two paths for flipping ID Verified outside the queue:
- Admin — open the user in edit mode and toggle the ID Verified checkbox.
- Admin + Shop Manager — without entering edit mode, click the Verify ID button next to the ID Verified badge. Visible whenever the user isn't already verified.
Either path:
- Requires step-up TOTP.
- Is recorded in the audit log as
id_verification_manual_override. - Auto-decides any in-flight request on that user (
false → trueauto-approves any pending request;true → falsesupersedes it).
Use this for users who were verified before this system existed, or for one-off corrections after a wrong decision. Prefer the queue flow for normal operations because it captures the photos, reason, and reviewer in one place.
Role permissions
| Role | List queue | View images | Decide | Manage settings |
|---|---|---|---|---|
| Admin | ✓ | ✓ | ✓ | ✓ |
| Shop Manager | ✓ | ✓ | ✓ | ✓ |
| Marketing | — | — | — | — |
Marketing users can't open the queue or settings tab. The image-viewing endpoint is gated server-side, not just on the nav.
Data retention
- Cloudflare image bodies are deleted three days after the decision (hourly cron). The window covers a weekend so you can spot a misfiled decision before photos disappear; after that, rely on the audit log and admin note instead of the source photos.
- The request record (status, reason, snapshot of name and email) is kept for audit purposes.
- After deletion the queue shows "images purged" against the request — you can still see metadata but not the photos.
- A user account deletion immediately purges that user's photos, regardless of the three-day window.
If Cloudflare is briefly unreachable the cron retries on the next run; an alarm fires after 24 failed attempts on a single image.
Pre-launch checklist
Run through this before flipping either gate on in production:
- [ ] Visit
/admin/id-verificationsand confirm at least one Pending or Approved row renders correctly. - [ ] Approve and reject one test request end-to-end. Confirm the user receives the email.
- [ ] Open a user who has
idVerified=truefrom before this system existed. Confirm the manual override checkbox still works. - [ ] Open the Settings tab. Toggle each gate on, confirm a non-verified test customer sees the banner on Wallet and Prizes, then toggle back off.
- [ ] Confirm the Marketing role can't see the queue.
GDPR and right-to-erasure
A user's right-to-erasure request triggers an immediate purge of that user's ID images from Cloudflare and clears the rejection reason / admin note from the request record. The request itself stays in the audit log with the deidentified snapshot fields. If you receive a SAR (subject access request), the export tooling pulls the request artefacts and a fresh signed URL.
If you have any doubt about whether a particular workflow is GDPR-compliant for your jurisdiction, escalate before approving or sharing.