KYC sessions, contact email & re-verification
Three endpoints back the KYC verification flow: one mints the hosted
verification URL your page opens in a popup, one refreshes the contact email on
a verification that’s already underway, and one sends an already-confirmed
player back through verification. Outcomes come back asynchronously via
the kyc_status_changed webhook.
Create a verification session
POST https://{operator}.app.lootboxsolutions.com/api/s2s/kyc/sessions{ "playerExternalId": "u_8431", "email": "player@example.com", "locale": "en", "returnOrigin": "https://casino.example"}| Field | Required | Notes |
|---|---|---|
playerExternalId | ✅ | Your stable opaque player id (1–190 chars). |
email | ✅ | Where the player’s verification emails land (see player emails). Held privately; never appears in URLs or webhooks. |
locale | ✅ | BCP-47-ish code (e.g. en, pt-BR). Language of the hosted verification page. |
returnOrigin | — | The exact origin (scheme + host + port) that receives the popup’s completion postMessage. Omit it and the popup posts nothing — you rely on the webhook. |
Response — 201
{ "kycUrl": "https://{operator}.app.lootboxsolutions.com/kyc?token=kt_…&locale=en", "expiresAt": "2026-07-07T12:30:00Z"}- Open
kycUrlin a popup window (window.open) — the page refuses framing, so it cannot go in an iframe. - The URL is valid for 30 minutes (
expiresAt) and single-use — the embedded token is consumed the moment the player opens the page. Mint a fresh one per click; an expired or reused link shows the player a “link expired” screen, not an error of yours. - Minting is idempotent-friendly by design: if the player already has an
open
PENDINGrequest, that request is reused (its contact fields refresh to what you just sent) and a new link is issued — any earlier link stops working. If their latest request was decided (or they never had one), a fresh request opens, which emits its ownPENDINGwebhook.
Errors
| code | HTTP | when |
|---|---|---|
KYC_DISABLED | 403 | KYC is not enabled for the operator |
KYC_ALREADY_SUBMITTED | 409 | the player’s verification is already submitted and awaiting review — nothing for them to redo |
| validation error | 422 | malformed input |
Update the contact email
Call this when a player changes their account email while a verification is open, so decision emails keep landing in the right inbox.
POST https://{operator}.app.lootboxsolutions.com/api/s2s/kyc/email{ "playerExternalId": "u_8431", "email": "new-address@example.com"}| Field | Required | Notes |
|---|---|---|
playerExternalId | ✅ | Same id the session was minted for. |
email | ✅ | The new contact address. |
Response — 200
{ "ok": true }Errors
| code | HTTP | when |
|---|---|---|
KYC_NO_OPEN_REQUEST | 404 | the player has no open (PENDING/SUBMITTED) verification — decided requests are immutable history |
KYC_NO_OPEN_REQUEST is a benign outcome, not a failure: it just means
there was nothing to update. Fire this call on every email change without
checking KYC state first and ignore the 404.
Re-verify a confirmed player
Call this when your side decides a confirmed player must pass verification
again — a risk review, a changed circumstance, whatever your compliance process
demands. It re-opens verification the same way every other re-verification
does: a fresh request is created, becoming the player’s latest, and its
PENDING event arrives on the
kyc_status_changed webhook
like any other. Your side should treat that as “no longer verified” until the
new request is confirmed.
POST https://{operator}.app.lootboxsolutions.com/api/s2s/kyc/reverify{ "playerExternalId": "u_8431", "reason": "Periodic review — verified identity is over 12 months old."}| Field | Required | Notes |
|---|---|---|
playerExternalId | ✅ | Your stable opaque player id (1–190 chars). |
reason | ✅ | 3–500 chars. Read by the platform reviewer who picks the re-verification up — tell them what to look at. It is never shown to the player. |
Response — 201 (review opened)
{ "status": "opened", "requestId": 977 }A new request now exists and its own PENDING webhook is on the way.
requestId is the new request’s id.
Response — 200 (already open)
{ "status": "already_open", "requestId": 977 }The player already has an open re-verification — this call created nothing and the earlier request (and its stated reason) stands. This is what makes the call safe to repeat: a double-click or a retried job cannot open a second review.
Errors
| code | HTTP | when |
|---|---|---|
KYC_DISABLED | 403 | KYC is not enabled for the operator |
KYC_NOT_CONFIRMED | 409 | the player has no confirmed verification to re-open — to verify someone for the first time (or after a rejection), mint a session instead |
| validation error | 422 | malformed input |
Two things this endpoint deliberately does not do:
- It never contacts the player. No email is sent and nothing changes in any
UI they can see, beyond whatever your own site does with the
PENDINGstatus. If and when the player should be asked to act is your call — when they are, mint a session and the open request is reused. - It never explains itself downstream. The webhook your re-verification
produces is indistinguishable from any other new request — the
reasonyou pass lives with the platform’s review desk only.