Public integration guide / agent-readable
Connect EVP identity evidence to XCIM consent.
This production-tested guide tells a coding agent exactly how to integrate a product with Emabled. EVP or OAuth establishes identity evidence. Emabled-hosted XCIM consent records the person's explicit communication choices, returns immediately to the product, and issues a signed, revocable receipt asynchronously.
Do not treat an EVP token, OAuth access token, OAuth ID token, or successful login as consent. Do not invent an email-reply flow. Only the explicit action on the Emabled-hosted Consent Manifest can grant the requested purposes. Identity must happen once; Hosted Consent must not start a second provider login after the application already supplied valid identity evidence.
01 / Quick start
Choose the identity path, then create one XCIM session.
Use the capability endpoint before selecting a path. Keep both profiles when your product wants progressive enhancement and reliable fallback.
GET https://api.emabled.com/v1/capabilities| Profile | Use when | What the agent must do |
|---|---|---|
evp-01 | Capability status is experimental and the verifier origin has a valid Origin Trial token. | Render the EVP email/token fields on the same origin as the token, then submit the token for Emabled validation. |
oidc-01 | EVP is unavailable, unsupported, or the product requires OAuth today. | Use the reviewed authorization-code relay and vendor backchannel. Keep the client secret on the vendor backend. |
| Both | Recommended for production pilots. | Send ["evp-01", "oidc-01"]; Emabled selects the available path without weakening consent. |
POST https://api.emabled.com/v1/consent-sessions
Authorization: Bearer <vendor-token>
Idempotency-Key: <unique-request-key>
Content-Type: application/json
{
"issuance_profile": "evp_hosted_consent",
"requested_recipient": "person@example.com",
"manifest_id": "urn:xcim:manifest:your-product-v3",
"return_uri": "https://app.example.com/xcim/complete",
"evp_delivery_mode": "vendor_origin_handoff",
"evp_nonce": "<32-byte base64url server challenge>",
"identity_profiles": ["evp-01", "oidc-01"],
"exchange_endpoint_id": "oxe_your_reviewed_endpoint"
}Choose one presentation mode: omit evp_delivery_mode for an Emabled-hosted EVP form, or send vendor_origin_handoff when your reviewed HTTPS origin owns the Origin Trial token. EVP-only sessions send identity_profiles: ["evp-01"] and omit exchange_endpoint_id. If the request includes oidc-01, the exchange endpoint must already be reviewed and active. Generate the EVP nonce as exactly 32 cryptographically random bytes encoded as unpadded base64url (43 characters); a UUID is not a valid substitute.
02 / Browser handoff
Redirect the browser; never simulate the hosted page.
Read hosted_consent_uri from the response and navigate the top-level browser to it. Emabled validates the supplied identity evidence, presents the frozen manifest, records the decision and redirects immediately. Receipt signing continues asynchronously.
Register the complete HTTPS return URI in Emabled. It is where Emabled sends the top-level browser after the consent decision. It is not Google's OAuth callback, not an access-token receiver, and not the EVP issuance endpoint. Do not trust a registration ID, email, receipt ID or success flag supplied only in its query string; recover the pending operation from server-side state.
03 / EVP path
The Origin Trial token belongs to the page origin.
The token must be issued for the exact HTTPS origin that serves the email form. A token for partners.lmgram.com cannot activate EVP on consent.emabled.com. Third-party Origin Trials are not currently supported.
https://consent.emabled.comUse this only when live capabilities report delivery_modes.emabled_hosted as available and Emabled serves the EVP form.
https://your-app.exampleUse vendor_origin_handoff after that exact origin has an active consent_presentation_delegate domain binding. The raw token travels only from the browser to your backend and then to Emabled.
<input name="email-address" type="email" autocomplete="email">
<input type="hidden" name="token"
nonce="<evp_handoff.nonce>"
autocomplete="email-verification-token">POST https://api.emabled.com/v1/consent-sessions/<session_id>/identity-evidence/evp
Authorization: Bearer <token with identity-evidence:submit>
Content-Type: application/json
{
"nonce": "<evp_handoff.nonce>",
"token": "<browser-populated EVP token>"
}- Prepare before the email interaction.Create a short-lived, single-use preparation on your backend. Generate a 32-byte base64url nonce and an unrelated opaque preparation ID. Render the same nonce on the hidden EVP field before the person selects an email; never generate or replace it in browser JavaScript.
- Keep one email control in one native form.The address requested by signup and the address verified by Chrome must be the same control. Do not ask for it twice, clone it, move it to a second screen, or inject the verifier form after page load.
- Bind without destroying browser evidence.Once the field is locally valid, bind the preparation to the normalized address and create the Emabled session using the already-rendered nonce. Changes to the address invalidate the preparation. A repeated request must be idempotent or replace only an expired preparation.
- Put the Origin Trial token on the same response as the form.Do not use a token issued for another host, subdomain, port, or protocol. The response must carry the public
Origin-Trialheader for that exact origin. - Post the native form to your own backend.Submit natively so Chrome can populate the token. JavaScript must not read, log, persist or forward the raw token. Your backend consumes the preparation once, verifies the selected email, then relays the token to Emabled over the authenticated backchannel.
- Redirect to
hosted_consent_uriafter verification.Identity evidence only unlocks the frozen manifest. The person must still make the explicit XCIM purpose decision. - Fall back when EVP is unavailable.Use the configured OAuth/OIDC path or your product's non-XCIM continuation policy. Never silently downgrade to email reply verification.
04 / OAuth path
Keep OAuth standard and keep secrets server-side.
For oidc-01, Emabled starts the reviewed authorization-code relay. The vendor exchanges the code with its own provider client and returns only the signed ID token and required metadata. OAuth is supported independently of EVP; it is both a fallback and a valid primary integration.
Do
- Register Emabled's callback URI in the vendor OAuth client.
- Validate exact state, nonce, PKCE, issuer, audience, time, and email policy.
- Return only the provider-signed ID token through the signed backchannel.
- Keep client secrets, access tokens, and refresh tokens in the vendor backend.
Do not
- Send a native Google access token to Emabled.
- Use a GIS popup callback as if it were an OAuth return URL.
- Let a vendor endpoint mark identity or consent as verified.
- Start a second login after the provider flow already returned.
A GIS JavaScript popup callback is not a redirect URI. You may keep the popup for your application's own login, then pass the provider-signed ID token only through an Emabled-reviewed first-party or backchannel binding. If that binding is not provisioned, create or use a Web OAuth client that supports the Emabled authorization-code callback. Never send a Google access token to Emabled and never replace your internal token-receiving API endpoint with the XCIM return URI.
Google OAuth callback https://id.emabled.com/oidc/callback/google
XCIM return URI https://your-app.example/xcim/complete
Hosted Consent https://consent.emabled.com/s/<session_id>
Receipt query https://api.emabled.com/v1/receipts/<receipt_id>05 / Reference lifecycle
Use server state to join the whole flow.
The production-tested pattern separates the browser's navigation state from the server's security state. It survives refreshes, cross-origin redirects, delayed receipts and a return that happens after a local preparation deadline.
// 1. Initial page response
preparation = createSingleUsePreparation()
nonce = base64url(randomBytes(32)) // exactly 43 chars
renderNativeEvpForm(preparation.id, nonce) // form exists in initial HTML
// 2. Valid email selection / blur
bind(preparation.id, normalize(email))
session = emabled.createConsentSession({ requested_recipient: email, evp_nonce: nonce })
store(preparation.id, session.id, session.hosted_consent_uri)
// 3. Native form POST populated by Chrome
consumeOnce(preparation.id)
emabled.submitEvpEvidence(session.id, nonce, browserToken)
redirect(session.hosted_consent_uri)
// 4. Exact return URI
pending = recoverFromHttpOnlyCookieOrOpaqueRecoveryId()
poll(emabledSessionId) // same-origin BFF recommended
verifyReceiptAndCurrentPermission()
materializeAccountOrRequestedOperation()Persist server-side
- Opaque preparation/recovery ID.
- Normalized email commitment and pending payload.
- Emabled session ID and exact hosted URI.
- Expiry, one-time consumption and terminal status.
Browser state
- Secure HttpOnly same-origin cookie as the primary binding.
- Opaque recovery ID in session storage only when refresh recovery is needed.
- No raw EVP token, provider token, receipt JWS or email in query parameters.
- Use a same-origin completion endpoint so secure cookies are sent reliably.
06 / Completion contract
Return immediately; verify the receipt server-side.
The browser return is a UX handoff and must not remain on Hosted Consent waiting for cryptography. Open the product shell immediately, then complete the security-sensitive operation only after the signed receipt is available and valid.
GET https://api.emabled.com/v1/consent-sessions/<session_id>
// The decision can be terminal before receipt issuance finishes:
{
"status": "accepted",
"identity_verified": true,
"receipt_id": null
}| Status | Agent action |
|---|---|
accepted + no receipt | Treat as receipt_pending. Keep the terminal decision recoverable, return the UI to the product and retry with bounded polling or a webhook. Do not expire an already accepted decision because local pending time elapsed. |
accepted + receipt | Verify the receipt JWS and current permission state, then materialize the account or protected operation idempotently. |
identity_unavailable / identity_failed | Offer OAuth fallback if configured or return to the product without claiming XCIM consent. |
identity_mismatch | Do not issue access or grant the requested purpose for the original address. Ask the user to choose the correct identity or cancel. |
cancelled / expired | Stop the XCIM branch. The product may continue only under its own independent policy. |
Verify Ed25519 against Emabled's current JWKS; bind receipt_id, application ID, manifest ID/hash, identity evidence and the expected permission; then query current revocation/permission state. An XCIM receipt may intentionally contain expires_at: null. Treat null as no receipt expiry and validate an expiry only when the field is present and non-null. Accept the serialization published by the receipt endpoint, including flattened JWS JSON when returned.
07 / Production troubleshooting
Diagnose the boundary that actually failed.
Do not restart the whole identity flow for every error. Preserve terminal decisions and use safe diagnostics that never log an email, nonce, token, receipt payload or browser evidence.
| Symptom | Likely cause and fix |
|---|---|
| No official Chrome checkmark | The requested address is not verified in the active Chrome profile, the person typed/pasted instead of selecting it, the form or nonce arrived too late, the form was injected or moved, contact-info verification is disabled, or the Origin Trial token does not match the exact origin. |
| Checkmark appears but Emabled rejects evidence | Start a fresh preparation and complete its native POST within the advertised session lifetime. Emabled measures EVP freshness from challenge creation, allows the configured clock skew, and still validates issuer signature, exact origin, nonce, browser key binding, token expiry and a maximum evidence age of five minutes. |
invalid_request creating the session | Confirm a 32-byte unpadded base64url nonce (43 characters), reviewed source/domain binding, active manifest, exact return URI and enabled capability. |
| EVP succeeds but Hosted Consent does not open | Confirm the native POST consumed the same preparation once, email and nonce match, and the vendor backend submitted evidence with the scoped identity-evidence:submit credential. |
| Decision button appears stuck | Query the session before asking the person to click again. The decision may already be terminal. Confirm the exact registered return origin is allowed by Hosted Consent's form-action CSP and let Emabled redirect directly. |
receipt_pending lasts unexpectedly | Keep polling without discarding accepted consent. Check worker database grants, KMS permissions and operation-level gRPC status. Encryption for stored identity evidence and the asserted-recipient receipt projection must use independent ciphertexts with their own authenticated contexts. |
| Return says invalid registration | Use a same-origin completion/BFF endpoint, a secure HttpOnly cookie and an opaque recovery ID. Cross-origin fetches do not send the pending cookie unless explicitly configured and should not be the default. |
| Second login shows provider picker or consent again | Reuse the application's authenticated session and existing terminal XCIM state. Start a new provider flow only for a new/expired identity operation or a materially changed manifest. |
08 / Agent checklist
Definition of done.
A coding agent can use this list as the acceptance gate before opening a pull request or deployment.
- Capabilities checked.Integration records whether
evp-01is available and retains a provisionedoidc-01path. - Application is registered.Application ID/key, HTTPS domain binding, multilingual manifest, exact return URI, pairwise key and any reviewed exchange endpoint are configured in Emabled.
- Identity is separate from consent.No token, login, webhook, or vendor API call directly grants a purpose, and Hosted Consent does not repeat identity already established.
- EVP page is native and early.The complete form, exact-origin token and 43-character nonce exist before email selection; one email field is used throughout.
- State is server-bound.A one-time preparation joins recipient, nonce, Emabled session and pending operation; return query parameters are never authoritative.
- Return does not block.Hosted Consent redirects immediately and the product handles
receipt_pendingasynchronously without discarding a terminal decision. - Secrets stay server-side.No client secret, access token, refresh token, raw EVT, browser key or decrypted address enters public logs, receipts, frontend bundles or URLs.
- Receipt is verified.Backend verifies the signed receipt, application/manifest binding and current permission/revocation state; nullable expiry is handled correctly.
- Recovery is tested.Hard refresh, delayed receipt, duplicate click, expired preparation, lost browser storage and idempotent completion have explicit tests.
- Fallback is explicit.Unsupported EVP never becomes an email-reply flow or an unverified vendor assertion.
Use the Emabled console to register sources, domains, OAuth bindings, return URLs, and exchange endpoints. For Origin Trial enablement on a custom vendor origin, contact support@weare14inc.com before deploying.