Embedded Member Integration
Some XKOVA flows run in a member's browser, not in your backend. A recipient claims value from an escrow, a sender signs a payment in their own wallet, a member signs in and manages their profile. These surfaces are built to be embedded directly in your web app, and each one carries its own credential so you never have to ship a tenant API key to the browser. This page covers the four member surfaces you embed, the tokens that authenticate them, and the origin and redirect allowlists that keep them safe.
What You Embed
Your backend holds the tenant API key and does the server-to-server work: creating payments, provisioning members, minting claim links. The browser handles the steps a person does in front of a screen. Those member surfaces authenticate with a scoped capability token, not your API key, so they are safe to call from client code.
| Surface | Credential | Runs in |
|---|---|---|
| Claim ceremony | The claim token in the URL path | Browser, directly against the API |
| Signing session | The HMAC signing token in the query string | Browser, directly against the API |
| Member session | Tenant API key to obtain, then a member access token | Your backend obtains it; the browser holds the returned token set |
| Avatar and receive QR | Tenant API key | Your backend; the browser does the direct-to-storage upload |
The Claim Ceremony
When a payment is sent to someone who is not yet a member, it lands in an escrow and XKOVA issues a claim link. The recipient opens that link in a browser and proves they control the contact on file before value is released. Every call is authenticated by the claim token in the path, so your front end drives the whole flow with no API key. See Escrow and Claims for how the escrow is created on the sender side.
- Fetch context.
GET /v1/claims/{token}returns the claim state (pending,claimed,cancelled, orexpired), theamount,token_symbolandtoken_decimals, therecipient_refthe code is delivered to, the token and claim expiries, anotp_verifiedflag, resolvedbranding, and theavailable_pathsthe recipient may choose. Render the landing page from this. - Send a code.
POST /v1/claims/{token}/send-otpwith{ channel, identifier }, wherechannelisemailorsms. The response carries achallenge_idand anexpires_at. - Verify the code.
POST /v1/claims/{token}/verify-otpwith{ challenge_id, code }, wherecodeis the six digits the recipient received. On success you get{ verified: true, resolve_grant }. Theresolve_grantis a one-time secret proving this caller passed the recipient-bound challenge; thread it into the next step. - Resolve.
POST /v1/claims/{token}/resolvewith{ path, payload, resolve_grant }. Thepathisexternal_wallet(release now topayload.address) orwait(leave the escrow open until expiry). Theresolve_grantis required, so a token holder who never verified the code cannot release the escrow.
flowchart TD
A[Open claim link in browser] --> B[GET claim context]
B --> C[Send OTP to email or sms]
C --> D[Verify OTP code]
D --> E{resolve_grant issued}
E --> F[Resolve: external_wallet]
E --> G[Resolve: wait until expiry]
F --> H[Value released to supplied wallet]
G --> I[Escrow stays open]
Two recovery calls handle a stale link. POST /v1/claims/{token}/refresh mints a fresh token
for the same escrow and returns the new claim_url. POST /v1/claims/{token}/resend
mails a fresh link to the recipient on file and always returns the same 202 body with no escrow
detail, so a holder of a leaked or expired token cannot fingerprint the escrow from the response.
The Member Session
The wrapped member-auth surface at /v1/member-auth/* gives your members email-and-password,
magic-link, and password-reset sign-in, plus TOTP MFA. Every endpoint requires a tenant-scoped API key, so
your backend calls this surface, then hands the returned token set to the browser. The member's session
token is what your client code carries afterward. The identity provider stays an internal detail behind this
wrapper; your integration only ever talks to XKOVA.
| Step | Endpoint | Returns |
|---|---|---|
| Register | POST /v1/member-auth/sign-up | A token set, or a pending-verification response when email confirm is required |
| Confirm email | POST /v1/member-auth/email/confirm | A token set, or an MFA challenge |
| Sign in | POST /v1/member-auth/sign-in | A token set, or an mfa_required challenge |
| Complete MFA | POST /v1/member-auth/sign-in/mfa/verify | The token set the first leg withheld |
| Magic link | POST /v1/member-auth/sign-in/magic-link/request then .../confirm | A sent acknowledgement, then a token set |
| Refresh | POST /v1/member-auth/refresh | A rotated token set |
| Read session | GET /v1/member-auth/session | The member profile for the current token |
| Sign out | POST /v1/member-auth/sign-out | 204 |
When a member has a verified MFA factor, sign-in returns no token at all. Branch on the response
status field: a value of mfa_required carries an mfa_token, an
expires_in, and a factors array, each entry a
{ factor_id, type, friendly_name }. You pass the mfa_token and the chosen
factor's factor_id with the member's code to
POST /v1/member-auth/sign-in/mfa/verify to obtain the session. Enrollment and management run at
POST /v1/member-auth/mfa/enroll, POST /v1/member-auth/mfa/{factorId}/verify,
GET /v1/member-auth/mfa, and DELETE /v1/member-auth/mfa/{factorId}. The SDK exposes
this surface as a wrapped member-auth client (signIn, verifySignInMfa,
refresh, session, and the rest) so you do not hand-roll the token branching; see
the SDK overview.
member_auth_redirect_url_not_allowlisted.Signing Sessions and the Wallet Handshake
A payment or escrow that needs a member's signature lands in a pending-sign state and XKOVA returns a
signing_url. That URL carries a short-lived HMAC token in its query string, and it is the
credential for the three signing endpoints, so the member signs directly from the browser with no API key.
This signature is what makes the transfer non-custodial: the member signs, a relayer submits, and the
contract verifies on chain before it moves anything. The full mechanics are in the
Non-custodial Model.
- Load the session.
GET /v1/payments/{id}/signing-session?token=...returns the resolvedbranding, a narrowpayment_preview(addresses, symbol, gross and net amounts, the fee snapshot, memo, and chain), and two EIP-712 typed-data documents:typed_datafor the transfer intent andpermit_typed_datafor the gasless token approval. Render the preview so the member sees exactly what they are signing. - Get the wallet token.
GET /v1/payments/{id}/member-jwt?token=...mints a short-lived JWT and returns{ jwt, expires_at, external_user_id }. The member's in-app wallet connects with this JWT; because the wallet address is derived deterministically from the token's subject, the member gets the same address every time. - Sign and submit. The wallet signs both typed-data documents, then you
POST /v1/payments/{id}/signatureswith the signatures and the same signing token in the body. XKOVA recovers the signer, builds the meta-transaction calldata, and the XKOVA Relayer submits it on chain and pays the gas.
uint256 as a decimal string because
JSON cannot carry a bigint. Restore each of those fields to a bigint before your wallet signs, or the
recovered signer will not match and the submit call returns authorization_invalid. A direct
send needs both signatures (the transfer intent and the permit); an escrow signature needs one.Escrows follow the same three-endpoint shape under /v1/escrows/{id}/..., with an
intent_type of create or cancel selecting which action the member is
signing. The signing session is issued only after the member's identity check clears; a member who is not
yet verified gets verification_required, and a payment with an open compliance case gets
compliance_case_open. See Identity Verification and
Payments.
Avatars and Receive QR
A member avatar uploads in three steps so the image bytes go straight to storage and never transit the API. Your backend mints the upload URL with the tenant API key; the browser does the direct upload.
POST /v1/account-holders/{id}/avatar/upload-urlwith the content type and length returns{ upload_url, object_key, expires_at }.- The browser sends a
PUTtoupload_urlwith the matchingContent-TypeandContent-Lengthheaders. The bytes go to storage, not to XKOVA. POST /v1/account-holders/{id}/avatar/confirmwith{ object_key }persists the avatar on the member and returns the updated account holder.
Read the current avatar with GET /v1/account-holders/{id}/avatar, which returns
{ url } as a short-lived presigned URL or null, and remove it with
DELETE /v1/account-holders/{id}/avatar. The resolved URL is safe to embed in your member app.
See Account Holders for the member record these attach to.
To let a member receive value by scan, POST /v1/wallets/{id}/qr generates a receive QR for a
wallet under the calling tenant. The body optionally carries an amount, a memo, and
a payment_request_id; the response is { uri, png_data_url }, where
png_data_url is a data URL you can render inline. See Wallets and
Balances.
Browser Security Posture
Two tenant-managed allowlists govern where member-auth flows may run and land. Manage them from your backend, then your embedded flows work from the origins you approved.
| Control | Endpoint | Enforced by |
|---|---|---|
| Allowed browser origins | GET / POST /v1/tenant/member-auth/allowed-origins, DELETE .../{id} | Member-auth requests from an origin not on the list are rejected with member_auth_origin_not_allowlisted |
| Allowed redirect URLs | GET / POST /v1/tenant/member-auth/redirect-urls, DELETE .../{id} | Email flows only redirect to an allowlisted URL; others are rejected with member_auth_redirect_url_not_allowlisted |
An allowed origin is an exact scheme://host[:port] match. A redirect URL is registered against
a purpose of email_confirm, password_reset, or magic_link,
and must match the value passed in the corresponding member-auth call. Register both before you ship the
member app, and add your local development origin during build-out; see Local
Development.
The credential rules follow from the surface. The tenant API key never belongs in client code: obtain
member tokens, signing tokens, and claim tokens server-side or from XKOVA, and let only those reach the
browser. Treat every capability token as a secret with a short life: claim tokens and signing tokens expire,
member access tokens rotate through refresh, and a leaked one grants only the single member or
transfer it names. Your tenant API key, by contrast, acts for the whole tenant, so its exposure is a
different order of problem. See Authentication for the full credential model.
Errors Worth Handling
The error catalog documents every code; these are the ones the member surfaces most commonly return.
| Surface | Codes |
|---|---|
| Claim ceremony | not_found, invalid_state (already claimed or expired), validation_failed |
| Member session | member_auth_invalid_credentials, member_auth_email_not_confirmed, member_auth_mfa_required, member_auth_mfa_code_invalid, member_auth_rate_limited, member_auth_redirect_url_not_allowlisted, member_auth_origin_not_allowlisted |
| Signing session | unauthorized (an expired or invalid signing token), verification_required, compliance_case_open, not_found |
| Submit signatures | invalid_state (already signed or past signing), authorization_invalid (the signature does not recover to the expected signer), service_unavailable |
Related
- Escrow and Claims: how the escrow behind a claim link is created.
- Payments: the lifecycle that produces a signing session.
- Non-custodial Model: why the member signature is the control.
- Branding: the branding resolved into claim and signing pages.