Embed: Hosted KYC
Hosted KYC lets you verify a user through a hosted UI. Create or update the user first, start a hosted verification, send the user to the hosted UI, then reconcile the final status through verification, user, and webhook endpoints.
Hosted KYC is asynchronous. Treat the start response as launch material for the hosted UI, not as a final verification decision.
How it works
A typical Hosted KYC integration follows five steps:
- Create or update the user and store the user's IIBAN.
- Start Hosted KYC for that user.
- Send the user to the hosted UI with the returned launch fields or a one-time link.
- Resume the same hosted verification if the user returns to an interrupted flow.
- Reconcile status with webhooks, hosted verification listing, and the user endpoint.
API surfaces
Use the API surface your integration already uses.
| API surface | Start Hosted KYC | User identifier |
|---|---|---|
| Embed REST | POST /b2b/verifications/{user}/start/hosted | user path parameter |
| Embed v1 | POST /private/StartHostedVerification | user query parameter |
Both surfaces return the same hosted launch fields and hosted verification status model.
Step 1: Create or update the user
Before starting Hosted KYC, create the user and provide the profile fields required for onboarding. Hosted KYC uses the user's existing profile. Do not send collected evidence or verification results in the hosted-start request.
Use these endpoints before starting verification:
| API surface | Create | Update | Read |
|---|---|---|---|
| Embed REST | POST /b2b/users | PATCH /b2b/users/{user} | GET /b2b/users/{user} |
| Embed v1 | POST /private/CreateUser | POST /private/UpdateUser | GET /private/GetUser |
For Embed REST, if the user has no non-CONCAT client identifier or tax ID, set declare_no_client_identifier to true in the Create User or Update User request. You can include a CONCAT client_identifier in the same request. If you omit client_identifier, include full_name, date_of_birth, and at least one value in nationalities so Payward can generate the CONCAT identifier.
Step 2: Start Hosted KYC
For Embed REST, call:
curl -X POST "https://nexus.kraken.com/b2b/verifications/$USER_IIBAN/start/hosted" \
-H "API-Key: $EMBED_API_KEY" \
-H "API-Nonce: $EMBED_API_NONCE" \
-H "API-Sign: $EMBED_API_SIGN" \
-H "Payward-Version: 2025-04-15" \
-H "Content-Type: application/json" \
-d '{}'
For Embed v1, call:
curl -X POST "https://api.kraken.com/0/private/StartHostedVerification?user=$USER_IIBAN" \
-H "API-Key: $EMBED_API_KEY" \
-H "API-Sign: $EMBED_API_SIGN" \
-H "Content-Type: application/json" \
-d '{"nonce":1616492376594}'
The response includes the public verification ID and hosted UI launch fields:
{
"result": {
"verification_id": "PVABCDE-FGHIJ-KLMNOP",
"inquiry_id": "inq_test_handle",
"session_token": "sess_test",
"environment_id": "env_test_handle"
}
}
Store verification_id. You need it to create one-time links, resume the hosted flow, reconcile status, and troubleshoot the user's hosted verification.
| Field | Description |
|---|---|
verification_id | Public ID for this hosted verification |
inquiry_id | Hosted verification flow identifier |
session_token | Short-lived launch token for the hosted UI |
environment_id | Hosted UI environment identifier returned with the launch token |
Step 3: Send the user to the hosted UI
Use the launch method agreed during onboarding.
For an embedded launch, use session_token and environment_id from the hosted-start or resume response. Treat session_token as short-lived and do not store it as a long-term credential.
For a redirect or shareable link, create a one-time link for the existing verification.
Embed REST:
curl -X POST "https://nexus.kraken.com/b2b/verifications/$USER_IIBAN/$VERIFICATION_ID/otl" \
-H "API-Key: $EMBED_API_KEY" \
-H "API-Nonce: $EMBED_API_NONCE" \
-H "API-Sign: $EMBED_API_SIGN" \
-H "Payward-Version: 2025-04-15"
Embed v1:
curl -X POST "https://api.kraken.com/0/private/CreateHostedVerificationLink?user=$USER_IIBAN&verification_id=$VERIFICATION_ID" \
-H "API-Key: $EMBED_API_KEY" \
-H "API-Sign: $EMBED_API_SIGN" \
-H "Content-Type: application/json" \
-d '{"nonce":1616492376595}'
The response includes a full URL and a shortened URL:
{
"result": {
"url": "https://verify.example.com/verify?code=us1-handle",
"url_short": "https://verify.example.com/v/HANDLE"
}
}
Send the user to url or url_short. One-time links are temporary. Create a fresh link if the user needs to return later.
Step 4: Resume an interrupted flow
If the user returns to an existing hosted verification, resume that verification instead of starting a duplicate one.
Embed REST:
curl -X POST "https://nexus.kraken.com/b2b/verifications/$USER_IIBAN/$VERIFICATION_ID/resume" \
-H "API-Key: $EMBED_API_KEY" \
-H "API-Nonce: $EMBED_API_NONCE" \
-H "API-Sign: $EMBED_API_SIGN" \
-H "Payward-Version: 2025-04-15"
Embed v1:
curl -X POST "https://api.kraken.com/0/private/ResumeHostedVerification?user=$USER_IIBAN&verification_id=$VERIFICATION_ID" \
-H "API-Key: $EMBED_API_KEY" \
-H "API-Sign: $EMBED_API_SIGN" \
-H "Content-Type: application/json" \
-d '{"nonce":1616492376596}'
The response returns fresh hosted UI launch fields:
{
"result": {
"inquiry_id": "inq_test_handle",
"session_token": "sess_test",
"environment_id": "env_test_handle"
}
}
Use the returned launch fields for embedded flows. If you need a URL, call the one-time link endpoint after resuming.
Step 5: Reconcile status
Hosted KYC does not finish in the start response. Reconcile status from webhooks and the latest API state.
Use GET /b2b/verifications/{user} or GET /private/ListVerifications to list verification attempts:
{
"result": {
"verifications": [
{
"verification_id": "PVABCDE-FGHIJ-KLMNOP",
"types": [
"proof_of_identity",
"proof_of_address",
"proof_of_liveness"
],
"method": "hosted",
"status": "pending",
"created_at": "2026-05-20T14:30:00Z",
"updated_at": "2026-05-20T14:45:00Z"
}
]
}
}
Use the verification status together with the user endpoint:
| Status | What it means | What to do |
|---|---|---|
started | The hosted verification was created | Send the user to the hosted UI |
pending | The verification is still in progress | Wait for an update, or resume the flow if the user returns |
completed | The hosted verification finished successfully | Fetch the user and confirm the final user status before enabling activity |
failed | The hosted verification did not complete | Fetch the user and verification state to determine the next available step |
The reasons field only appears when a verification status is failed.
Webhook events
Register webhooks for user lifecycle events so your system does not need to poll continuously. Webhook events are signals to fetch the latest state, not the complete state by themselves.
| Event | Meaning | Recommended action |
|---|---|---|
user.verification_needed | The user must complete an additional verification step before continuing | Fetch the user and verification state, then send the user back to Hosted KYC |
user.verification_failed | The user's verification attempt failed | Fetch the user and verification state to determine whether another step is needed |
user.verified | The user has been verified and can trade on the platform | Fetch the user, update your local status, and enable eligible activity |
user.closed | The user's account has been permanently closed | Stop account activity and update your local status |
user.disabled | The user's account has been disabled | Stop account activity and wait for the latest user state |
Webhook delivery can be repeated. Deduplicate events, return a 2xx response only after accepting the event, and fetch the latest user and verification state before changing what the user can do.
Retry guidance
Use the hosted-start endpoint to create a hosted verification. Do not call it repeatedly to refresh a session or link.
Use these follow-up endpoints for the same verification_id:
| Need | Embed REST | Embed v1 |
|---|---|---|
| Create a fresh one-time link | POST /b2b/verifications/{user}/{verification_id}/otl | POST /private/CreateHostedVerificationLink |
| Refresh launch fields | POST /b2b/verifications/{user}/{verification_id}/resume | POST /private/ResumeHostedVerification |
| List verification attempts | GET /b2b/verifications/{user} | GET /private/ListVerifications |
| Check user state | GET /b2b/users/{user} | GET /private/GetUser |
Only start a new hosted verification after a previous attempt reaches a terminal state and your integration allows another attempt.