Skip to main content
A published flow may carry a return URL. When a hosted session under that flow finishes, the completion screen says only that the submission is done, shows a Return button as a fallback, and then sends the browser to your URL. The redirect carries four query parameters, all set by Esectra:
For example:
Nothing else is in the URL. No verdict, no risk level, no reasons, no document data, no API key, and never the capture token - the code is minted separately, after the session has concluded, and the two have nothing in common.
kyc_id and state are correlation data, not a result. Anyone can type a URL into a browser. Treat the redirect as “a session for this reference may have finished”: exchange the code server-to-server, and decide from the exchange answer alone.

What to put in subject_id

Because kyc_id travels in a URL, it will appear in browser history, in your web server’s access log and in any proxy or CDN log between the person and you. Make subject_id an opaque reference - an id from your own system that means nothing outside it, such as kyc_t5l7ogyk9m8v09mj - and never a name, an email address, a phone number, a national id number or anything else that identifies the person on its own. Two rules apply, at two moments:
  • At open, the value is bounded the way state is: non-empty, at most 200 characters, no control characters. Anything else is refused with 422 SUBJECT_TOO_LONG or 422 SUBJECT_INVALID, on the hosted and the server-to-server path alike. Nothing narrower is refused here - a name with a space or an email address still opens a session - so references your integration already uses keep working.
  • At the redirect, kyc_id is sent only when the stored value is an opaque URL token: ASCII letters, digits, -, _, . and ~, nothing else, and not a run of eight or more digits alone. A value outside that - jane.doe@example.com, Jane Doe, +2348012345678, Doe,Jane, acct/42, a non-ASCII letter, 12345678901 - opens and completes normally but is not put in the URL; the redirect then carries code, session_id and state only, the exchange still names the subject, and the RESULT_CODE_ISSUED audit event carries kyc_id_omitted with NOT_OPAQUE, DIGITS_ONLY, or TOO_LONG / EMPTY / CONTROL_CHARACTER for a session stored before the open-time rule.
This guard is a shape check, not a judgement. It stops the common shapes of personal data - an email address, a name with a space or punctuation, a phone number, a long run of digits such as a national identity or bank verification number - but it cannot recognise a surname written alone (Okonkwo), a date of birth with separators (1990-05-14), or a short number that happens to identify someone. Such a value fits and will be sent. If any of your existing sessions were opened with one as subject_id, the first completion after this version is deployed puts it in the redirect URL, in browser history and in your access logs. Review what you send as subject_id before publishing a flow with a return URL, or leave the return URL empty until you have.

Configure the return URL

The URL belongs to the flow, set by an organisation admin, and is frozen into each session when it opens. Nothing in the capture link, the query string or the person’s browser can change where they are sent. It must be https with a host and no credentials or #fragment; a deployment running a development profile also accepts http://localhost and http://127.0.0.1. Query parameters of your own are kept, in the order you wrote them, and Esectra’s four follow. Your URL may not name code, session_id, kyc_id or state itself, in any letter case or percent-encoded spelling: saving or publishing such a flow is refused with 422 INVALID_FLOW and a message naming the parameter, so the redirect can never carry two values for one name or a value you set where your server expects Esectra’s.

Exchange the code

Your server receives the redirect and exchanges the code under its API key. The answer names the session’s subject_id; check it against the kyc_id you were redirected with before you act on the result, and treat a mismatch as a request to ignore:
The answer is the session’s result:
Reasons and check details are worded from fixed vocabularies keyed on stable codes - the same wording Control shows. No score, model name or threshold is ever in them; a stored code the API does not describe is reported as UNSPECIFIED. The exchange is atomic and single-use: the code is bound to your tenant, spent by the request that wins, and refused afterwards. Refusals are 404 RESULT_CODE_INVALID (no such code for your tenant - another tenant’s code looks exactly like this), 409 RESULT_CODE_CONSUMED (already exchanged) and 410 RESULT_CODE_EXPIRED. If a store fails after the code has been spent, the answer is 503 RESULT_UNAVAILABLE_AFTER_EXCHANGE and its message names the session id to read instead; the code is not given back.

Read the result without the code

The code is a pointer, not the only way in. Read the same result at any time by session id with your API key, so a lost exchange response or a missed webhook costs nothing - and nothing here depends on a webhook having arrived:
A session that has not finished reads as status: "PENDING" with no decision. A completion that fails on the server - the session could not be marked finished - issues no code and sends the person nowhere; the page stays on its own screen.

What is recorded

Issuing, exchanging and refusing a code are audit events (RESULT_CODE_ISSUED, RESULT_CODE_EXCHANGED, RESULT_CODE_REFUSED) that name the session and the outcome; RESULT_CODE_ISSUED carries kyc_id_omitted when the redirect went out without a kyc_id. A completion that could not be given a redirect at all - the frozen return URL no longer passes this deployment’s rules, or the code could not be stored - is RETURN_REDIRECT_NOT_ISSUED with the cause; the person still sees the finished screen, and the result is readable by session id. The code itself is stored only as a hash and appears in no log or audit event. A return URL published before code, session_id, kyc_id and state were reserved keeps working: the stale parameter is dropped and the server’s value sent, each name exactly once. It cannot be re-saved until the parameter is removed.