Speak with an Expert

Workflow API

SEON's Workflow API enables you to initialize and manage verification workflows that combine document verification, selfie checks, fraud detection, and AML screening in a single orchestrated flow. Use it to start a workflow session and receive a token for the frontend SDK, to read the results of an execution on demand, and to record your own decision on it.

For more context on how to begin your API integration check the Introduction section or the Integration Guide.

Good to know

  • The workflowId must be a valid UUID of an active workflow created in the Admin Panel (Admin Panel / Workflows).
  • The user_id field is always required in the inputs object to identify the end user.
  • Additional required inputs depend on your workflow configuration (e.g. email if Email check is enabled, phone_number if Phone check is enabled).
  • All SEON API requests are case-sensitive. Please follow the formatting below to avoid errors.
  • IP address is auto-captured from the end user's browser if not provided in the request.
  • Device fingerprinting is handled automatically by the SDK when Device check is enabled.
  • All Fraud API input fields are accepted. The Workflow API supports the complete set of fields from the Fraud API, plus additional orchestration-specific fields (e.g. reference_image, eKYC identifiers). See the Fraud API documentation for the full list of available fields.

Common Workflow Scenarios

Workflow typeRequired inputs
Document + Selfie (basic)user_id
Document + Selfie + Face Match (URL)user_id, reference_image
Document + Selfie + Proof of Address (Evidence Collection)user_id, user_address
Email + Phone fraud checkuser_id, email, phone_number
Full fraud check (Email + Phone + IP)user_id, email, phone_number (IP auto-captured)
AML screeninguser_id, user_fullname
NIN eKYC (Nigeria)user_id, user_firstname, user_lastname, user_dob, nin
BVN eKYC (Nigeria)user_id, user_firstname, user_lastname, user_dob, bvn
CPF eKYC (Brazil)user_id, cpf

Request

POSThttps://api.seon.io/orchestration-api/v1/init-workflow
Header x-api-key: Your SEON API key from Admin Panel / Settings / API Keys.
Regional endpoints: US https://api.us-east-1-main.seon.io/orchestration-api/v1/init-workflow · APAC https://api.ap-southeast-1-main.seon.io/orchestration-api/v1/init-workflow

Request attributes

workflowIdstring (uuid)required

The unique identifier of the workflow to execute. Obtain from Admin Panel / Workflows.

inputsobjectrequired

Workflow input parameters. user_id is always required; other required inputs depend on your workflow configuration.

34 child attributes
user_idstringrequired

Your user's unique identifier in your system. Always required.

emailstring (email)conditional

Full email address. Required if Email check is enabled in your workflow.

phone_numberstringconditional

Phone number with country code (max 19 chars). Required if Phone check is enabled.

max 19 chars
ipstring

User's IP address. Auto-captured from the browser if not provided.

user_fullnamestringconditional

User's full name. Required for AML check or Document verification if set to Sent with session trigger.

user_firstnamestringconditional

User's first name. Required for NIN/BVN eKYC checks.

user_middlenamestring

User's middle name.

user_lastnamestringconditional

User's last name. Required for NIN/BVN eKYC checks.

user_dobstring (date)conditional

Date of birth in YYYY-MM-DD format. Required for NIN/BVN/CURP/SSN eKYC checks.

reference_imagestring (uri)conditional

URL to a reference image for face match. Required if Face match is enabled and set to Sent with session trigger.

ninstringconditional

Nigerian National ID Number (11 digits). Required for NIN eKYC.

bvnstringconditional

Bank Verification Number (11 digits). Required for BVN eKYC.

cpfstringconditional

Brazilian tax identifier (format: 123.456.789-00). Required for CPF eKYC.

curpstringconditional

Mexican population ID (18 characters). Required for CURP eKYC.

ssnstringconditional

US Social Security Number (format: 123-45-6789). Required for SSN eKYC.

aadhaarstringconditional

Indian Aadhaar identifier (12 digits). Required for Aadhaar eKYC.

sessionstring

Device fingerprint. Auto-collected by the SDK.

device_idstring

Third-party device fingerprint ID.

user_pobstring

Place of birth.

user_photoid_numberstring

Photo ID number.

user_countrystring

ISO 3166-1 two-character country code.

user_citystring

City name.

user_regionstring

ISO 3166-2 two-character region code.

user_zipstring

Postal/zip code.

user_streetstring

Street address line 1.

user_street2string

Street address line 2.

user_addressstringconditional

Full address. Required for Address verification if set to Sent with session trigger.

user_genderstring

User gender.

user_creatednumber

User registration date (UNIX timestamp).

transaction_idstring

Unique transaction identifier.

transaction_typestring

Transaction type (e.g. purchase).

transaction_amountnumber

Transaction amount (decimal, e.g. 539.99).

transaction_currencystring

ISO 4217 currency code (e.g. USD).

custom_fieldsobject

Key-value pairs for custom data points.

Code samples

curl -X POST "https://api.seon.io/orchestration-api/v1/init-workflow" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $SEON_API_KEY" \
  -d '{
    "workflowId": "550e8400-e29b-41d4-a716-446655440000",
    "inputs": {
      "user_id": "user-12345",
      "email": "john.doe@example.com",
      "phone_number": "+14155551234"
    }
  }'
import os
import requests

response = requests.post(
    "https://api.seon.io/orchestration-api/v1/init-workflow",
    headers={
        "Content-Type": "application/json",
        "x-api-key": os.environ["SEON_API_KEY"],
    },
    json={
        "workflowId": "550e8400-e29b-41d4-a716-446655440000",
        "inputs": {
            "user_id": "user-12345",
            "email": "john.doe@example.com",
            "phone_number": "+14155551234",
        },
    },
)

data = response.json()["data"]
print(data["token"], data["executionId"])
const response = await fetch("https://api.seon.io/orchestration-api/v1/init-workflow", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.SEON_API_KEY,
  },
  body: JSON.stringify({
    workflowId: "550e8400-e29b-41d4-a716-446655440000",
    inputs: {
      user_id: "user-12345",
      email: "john.doe@example.com",
      phone_number: "+14155551234",
    },
  }),
});

const { data } = await response.json();
console.log(data.token, data.executionId);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class InitWorkflow {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("SEON_API_KEY");

        String payload = """
            {
                "workflowId": "550e8400-e29b-41d4-a716-446655440000",
                "inputs": {
                    "user_id": "user-12345",
                    "email": "john.doe@example.com",
                    "phone_number": "+14155551234"
                }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.seon.io/orchestration-api/v1/init-workflow"))
            .header("Content-Type", "application/json")
            .header("x-api-key", apiKey)
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
<?php
$apiKey = getenv('SEON_API_KEY');

$payload = [
    'workflowId' => '550e8400-e29b-41d4-a716-446655440000',
    'inputs' => [
        'user_id' => 'user-12345',
        'email' => 'john.doe@example.com',
        'phone_number' => '+14155551234',
    ],
];

$ch = curl_init('https://api.seon.io/orchestration-api/v1/init-workflow');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        "x-api-key: {$apiKey}",
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true)['data'];
echo $data['token'] . "\n";
echo $data['executionId'] . "\n";

Response

The endpoint returns a JSON structured response.

dataobjectrequired
4 child attributes
executionIdstring (uuid)required

Unique identifier for this workflow execution. Store it — it is the id of the execution in webhooks and in the results endpoint.

tokenstringrequired

Opaque session token to pass to the frontend SDK to start the verification flow. Do not parse or store it beyond the session.

expiresAtstring (date-time)

When this execution can no longer be started by the SDK. Initialize the SDK before this time.

requiresClientInitboolean

When false, the workflow has no interactive steps and runs to completion on its own — you can skip the SDK and wait for the webhook. Treat an absent value as true.

Default true
{
  "data": {
    "executionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Error Responses

The init-workflow and workflow-link endpoints return the same error responses. The execution endpoints below list their own.

HTTP statusError codeDescription
400MISSING_REQUIRED_INPUTS

Required workflow inputs not provided (e.g. missing user_id or workflow-specific fields).

INVALID_INPUT_FORMAT

An input field is malformed (e.g. invalid email format).

401UNAUTHORIZED

Invalid or missing API key.

403FORBIDDEN

API key doesn't have access to this workflow.

404WORKFLOW_NOT_FOUND

Workflow ID doesn't exist or the workflow is inactive.

429RATE_LIMITED

Too many requests. Implement exponential backoff.

500INTERNAL_ERROR

Internal server error. Contact SEON support with your workflowId.

Retrieving workflow execution results

Once an execution has started you can read its current state and, once it has finished, the result of every check it ran. The object returned in data is the same object the workflow webhooks deliver, so use this endpoint to fetch results on demand — after a missed webhook, to download captured media later, or to reconcile — rather than as your primary way of receiving results.

GEThttps://api.seon.io/orchestration-api/v1/workflow-execution/{id}
Header x-api-key: Your SEON API key from Admin Panel / Settings / API Keys.
Regional endpoints: US https://api.us-east-1-main.seon.io/orchestration-api/v1/workflow-execution/{id} · APAC https://api.ap-southeast-1-main.seon.io/orchestration-api/v1/workflow-execution/{id}
ParameterInTypeDescription
idpathstring (uuid)The executionId returned by init-workflow or workflow-link.
mediaOnlyquerybooleanOptional, default false. When true, each entry in checks carries only checkType and capturedMedia. Use it to download images and video without the full result payload.
curl "https://api.seon.io/orchestration-api/v1/workflow-execution/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "x-api-key: $SEON_API_KEY"
import os
import requests

execution_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
response = requests.get(
    f"https://api.seon.io/orchestration-api/v1/workflow-execution/{execution_id}",
    headers={"x-api-key": os.environ["SEON_API_KEY"]},
)

data = response.json()["data"]
print(data["status"], [c["checkType"] for c in data["checks"] or []])
const executionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const response = await fetch(
  `https://api.seon.io/orchestration-api/v1/workflow-execution/${executionId}`,
  { headers: { "x-api-key": process.env.SEON_API_KEY } },
);

const { data } = await response.json();
console.log(data.status, data.checks?.map((c) => c.checkType));

Workflow execution object

dataobjectrequired
7 child attributes
idstring (uuid)required

The workflow execution id (the executionId you received when starting it).

statusstringrequired

Overall outcome of the execution. PENDING while the user is still going through the flow.

One of PENDING, APPROVED, REVIEW, DECLINED, EXPIRED, ERROR
workflowobjectrequired
2 child attributes
idstring (uuid)required

The workflow id.

namestringrequired

The workflow name as shown in the Admin Panel.

createdAtstring (date-time)required

When the execution was created.

dataPurgedAtstring (date-time)required

Set once personal data has been removed under your data retention settings. After this, check entries keep only their type, status and timestamps.

loipobject

Level of Identity Proofing evaluation. Present only when your workflow is configured for it.

3 child attributes
resultstringrequired

The level of identity proofing reached.

One of BASELINE, EXTENDED, NONE, NOT_PERFORMED
evaluatedAtstring (date-time)required

When the level was evaluated.

unmetConditionsarray of object

Present only when result is NONE — the conditions that prevented a higher level.

2 child attributes
conditionstringrequired

Identifier of the unmet condition.

reasonstring

Human-readable explanation.

checksarray of objectrequired

One entry per check the workflow ran, ordered by startedAt. null while the execution is PENDING or when check details are temporarily unavailable.

21 child attributes
checkTypestringrequired

Which check produced this entry.

One of DOCUMENT_CHECK, SELFIE_CHECK, POA_CHECK, CREDIT_CARD_CHECK, EVIDENCE_COLLECTION, FRAUD_API
statusstringrequired

Outcome of this check.

One of APPROVED, REVIEW, DECLINED, EXPIRED, ERROR, RETRY_REQUIRED
sessionIdstring (uuid)

Identity verification session id for document, selfie, address, card and evidence checks. null for FRAUD_API.

transactionIdstring

Fraud API transaction id for FRAUD_API checks. null for identity checks.

startedAtstring (date-time)required
finishedAtstring (date-time)
platformstring

Platform the user completed the check on.

One of WEB, IOS, ANDROID
duplicatesFoundboolean

Whether duplicate detection matched this user against earlier sessions.

statusDetailstring

Additional information about the status, when available.

referenceIdstring
emailstring
phoneNumberstring
userIdstring

The user_id you supplied when starting the workflow.

documentCheckResultobject

Sub-check outcomes of a document check. Same field-level schema as the identity verification session webhook.

documentCheckExtractedDataobject

Data extracted from the document (name, date of birth, document number, expiry, ...).

selfieVerificationResultobject

Liveness and face match outcomes of a selfie check.

proofOfAddressCheckResultobject
proofOfAddressExtractedDataobject
creditCardVerificationResultobject
evidenceCollectionCheckResultobject
capturedMediaobject

Links to the images and video captured during the check. Links are time-limited.

{
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "APPROVED",
    "workflow": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "KYC Onboarding Flow"
    },
    "createdAt": "2026-08-20T09:12:31.000Z",
    "dataPurgedAt": null,
    "loip": {
      "result": "BASELINE",
      "evaluatedAt": "2026-08-20T09:16:02.369Z"
    },
    "checks": [
      {
        "checkType": "DOCUMENT_CHECK",
        "status": "APPROVED",
        "sessionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "transactionId": null,
        "startedAt": "2026-08-20T09:12:40.000Z",
        "finishedAt": "2026-08-20T09:14:02.000Z",
        "platform": "WEB",
        "duplicatesFound": false,
        "statusDetail": null,
        "referenceId": null,
        "email": "john.doe@example.com",
        "phoneNumber": null,
        "userId": "user-12345",
        "documentCheckResult": {
          "overallResult": "APPROVED",
          "documentValidityCheckResult": "PASS",
          "imageQualityCheckResult": "PASS"
        },
        "documentCheckExtractedData": {
          "fullName": "JOHN DOE",
          "birthDate": "1990-05-15",
          "documentType": "PASSPORT",
          "country": "US"
        },
        "selfieVerificationResult": null,
        "proofOfAddressCheckResult": null,
        "proofOfAddressExtractedData": null,
        "creditCardVerificationResult": null,
        "evidenceCollectionCheckResult": null,
        "capturedMedia": null
      },
      {
        "checkType": "SELFIE_CHECK",
        "status": "APPROVED",
        "sessionId": "9b2f1c3e-5d4a-4f6b-8c7d-0e1f2a3b4c5d",
        "transactionId": null,
        "startedAt": "2026-08-20T09:14:05.000Z",
        "finishedAt": "2026-08-20T09:15:10.000Z",
        "platform": "WEB",
        "duplicatesFound": false,
        "statusDetail": null,
        "referenceId": null,
        "email": "john.doe@example.com",
        "phoneNumber": null,
        "userId": "user-12345",
        "documentCheckResult": null,
        "documentCheckExtractedData": null,
        "selfieVerificationResult": {
          "overallResult": "APPROVED",
          "livenessCheckResult": "PASS",
          "faceMatchingResult": "PASS"
        },
        "proofOfAddressCheckResult": null,
        "proofOfAddressExtractedData": null,
        "creditCardVerificationResult": null,
        "evidenceCollectionCheckResult": null,
        "capturedMedia": null
      },
      {
        "checkType": "FRAUD_API",
        "status": "APPROVED",
        "sessionId": null,
        "transactionId": "98db9a56b2e3",
        "startedAt": "2026-08-20T09:15:11.000Z",
        "finishedAt": "2026-08-20T09:15:12.000Z",
        "platform": null,
        "duplicatesFound": null,
        "statusDetail": null,
        "referenceId": null,
        "email": "john.doe@example.com",
        "phoneNumber": "+14155551234",
        "userId": "user-12345",
        "documentCheckResult": null,
        "documentCheckExtractedData": null,
        "selfieVerificationResult": null,
        "proofOfAddressCheckResult": null,
        "proofOfAddressExtractedData": null,
        "creditCardVerificationResult": null,
        "evidenceCollectionCheckResult": null,
        "capturedMedia": null
      }
    ]
  }
}

Good to know about execution results

  • status is PENDING while the user is still in the flow. checks is null until the execution finishes.
  • A negative outcome is DECLINED, both for the execution and for individual checks.
  • Identity checks (DOCUMENT_CHECK, SELFIE_CHECK, POA_CHECK, CREDIT_CARD_CHECK, EVIDENCE_COLLECTION) carry a sessionId; the FRAUD_API check carries a transactionId instead. Result objects such as documentCheckResult use the same field-level schema as the identity verification session webhook.
  • loip is present only when your workflow evaluates a Level of Identity Proofing, and unmetConditions appears only when the result is NONE.
  • After personal data is purged under your retention settings, dataPurgedAt is set and each check keeps only its type, status and timestamps; result and extracted-data fields are null and mediaOnly returns an empty list.
HTTP statusError codeDescription
401UNAUTHORIZED

Invalid or missing API key.

404WORKFLOW_EXECUTION_NOT_FOUND

No workflow execution with this id exists for your account.

429RATE_LIMITED

Too many requests. Implement exponential backoff.

500INTERNAL_ERROR

Internal server error. Contact SEON support with your workflowId.

Recording a decision

If you make your own final decision on a verification — for example after a manual review on your side — record it on the execution so that the Admin Panel, your webhooks and later reads all reflect it.

PATCHhttps://api.seon.io/orchestration-api/v1/workflow-execution/{id}
Header x-api-key: Your SEON API key from Admin Panel / Settings / API Keys.
Regional endpoints: US https://api.us-east-1-main.seon.io/orchestration-api/v1/workflow-execution/{id} · APAC https://api.ap-southeast-1-main.seon.io/orchestration-api/v1/workflow-execution/{id}
statusstringrequired

Your final decision on the execution.

One of APPROVED, DECLINED
commentstring

Optional note shown alongside the decision in the Admin Panel.

max 250 chars
{
  "status": "APPROVED",
  "comment": "Approved after manual review of the address document."
}
curl -X PATCH "https://api.seon.io/orchestration-api/v1/workflow-execution/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $SEON_API_KEY" \
  -d '{ "status": "APPROVED", "comment": "Approved after manual review." }'
import os
import requests

execution_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
response = requests.patch(
    f"https://api.seon.io/orchestration-api/v1/workflow-execution/{execution_id}",
    headers={
        "Content-Type": "application/json",
        "x-api-key": os.environ["SEON_API_KEY"],
    },
    json={"status": "APPROVED", "comment": "Approved after manual review."},
)
response.raise_for_status()
const executionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
await fetch(`https://api.seon.io/orchestration-api/v1/workflow-execution/${executionId}`, {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.SEON_API_KEY,
  },
  body: JSON.stringify({ status: "APPROVED", comment: "Approved after manual review." }),
});
  • Decisions can be recorded only on finished executions (APPROVED, REVIEW, DECLINED or EXPIRED), not while one is PENDING or after an ERROR.
  • A recorded decision triggers a workflow_execution_updated webhook carrying the new status.
  • The response data is an empty object; a 200 status means the decision was stored.
HTTP statusError codeDescription
400INVALID_REQUEST_BODY

status is missing or not one of APPROVED, DECLINED, or comment exceeds 250 characters.

EXECUTION_IN_PROGRESS

The execution is still pending or ended in an error. Decisions can only be recorded on finished executions.

EXECUTION_PURGED

The execution's data has already been purged under your data retention settings.

401UNAUTHORIZED

Invalid or missing API key.

404WORKFLOW_EXECUTION_NOT_FOUND

No workflow execution with this id exists for your account.

429RATE_LIMITED

Too many requests. Implement exponential backoff.

500INTERNAL_ERROR

Internal server error. Contact SEON support with your workflowId.

Workflow webhooks

Webhooks are the authoritative way to receive results. Subscribe to the events below in Admin Panel / Settings / Webhooks; for endpoint setup, signature verification and retries see the Webhooks reference.

EventWhen it is sent
orchestration/workflow_execution_finishedThe execution reached a final status. This is the event to drive your decisioning from.
orchestration/workflow_execution_updatedThe status was changed after completion — by an analyst in the Admin Panel or through the decision endpoint.
orchestration/workflow_execution_syncSent on demand when someone chooses to re-send a finished execution's data from the Workflow Runs page in the Admin Panel. Useful to recover a missed delivery. It is delivered once, without retries, and only if you are subscribed to it.

Every event carries the same payload: the envelope below, with data being the workflow execution object.

eventstringrequired

Which event this delivery is for.

One of orchestration/workflow_execution_finished, orchestration/workflow_execution_updated, orchestration/workflow_execution_sync
timestampstring (date-time)required

When the event was emitted.

dataobjectrequired
7 child attributes
idstring (uuid)required

The workflow execution id (the executionId you received when starting it).

statusstringrequired

Overall outcome of the execution. PENDING while the user is still going through the flow.

One of PENDING, APPROVED, REVIEW, DECLINED, EXPIRED, ERROR
workflowobjectrequired
2 child attributes
idstring (uuid)required

The workflow id.

namestringrequired

The workflow name as shown in the Admin Panel.

createdAtstring (date-time)required

When the execution was created.

dataPurgedAtstring (date-time)required

Set once personal data has been removed under your data retention settings. After this, check entries keep only their type, status and timestamps.

loipobject

Level of Identity Proofing evaluation. Present only when your workflow is configured for it.

3 child attributes
resultstringrequired

The level of identity proofing reached.

One of BASELINE, EXTENDED, NONE, NOT_PERFORMED
evaluatedAtstring (date-time)required

When the level was evaluated.

unmetConditionsarray of object

Present only when result is NONE — the conditions that prevented a higher level.

2 child attributes
conditionstringrequired

Identifier of the unmet condition.

reasonstring

Human-readable explanation.

checksarray of objectrequired

One entry per check the workflow ran, ordered by startedAt. null while the execution is PENDING or when check details are temporarily unavailable.

21 child attributes
checkTypestringrequired

Which check produced this entry.

One of DOCUMENT_CHECK, SELFIE_CHECK, POA_CHECK, CREDIT_CARD_CHECK, EVIDENCE_COLLECTION, FRAUD_API
statusstringrequired

Outcome of this check.

One of APPROVED, REVIEW, DECLINED, EXPIRED, ERROR, RETRY_REQUIRED
sessionIdstring (uuid)

Identity verification session id for document, selfie, address, card and evidence checks. null for FRAUD_API.

transactionIdstring

Fraud API transaction id for FRAUD_API checks. null for identity checks.

startedAtstring (date-time)required
finishedAtstring (date-time)
platformstring

Platform the user completed the check on.

One of WEB, IOS, ANDROID
duplicatesFoundboolean

Whether duplicate detection matched this user against earlier sessions.

statusDetailstring

Additional information about the status, when available.

referenceIdstring
emailstring
phoneNumberstring
userIdstring

The user_id you supplied when starting the workflow.

documentCheckResultobject

Sub-check outcomes of a document check. Same field-level schema as the identity verification session webhook.

documentCheckExtractedDataobject

Data extracted from the document (name, date of birth, document number, expiry, ...).

selfieVerificationResultobject

Liveness and face match outcomes of a selfie check.

proofOfAddressCheckResultobject
proofOfAddressExtractedDataobject
creditCardVerificationResultobject
evidenceCollectionCheckResultobject
capturedMediaobject

Links to the images and video captured during the check. Links are time-limited.

The workflow-link endpoint provides an alternative to the standard Workflow API, allowing you to initiate workflow executions by generating shareable verification links instead of integrating with the SDK. This approach is ideal when you want to send verification URLs directly to end users via email, SMS, or other channels without the need of a frontend SDK integration.

  • This endpoint returns error responses identical to the Workflow API, and shares its request schema with three additional optional attributes specific to workflow links — expiresIn, completedUrl and incompleteUrl.
  • Webhook notifications and callbacks work in the same way as with the standard Workflow API endpoint.
  • The returned redirectUrl can be shared directly with end users — no frontend SDK integration required.
  • Workflow links are valid for 7 days by default. If the user does not complete the verification within this period, the execution status becomes EXPIRED.
  • Each API call creates a new workflow execution. To send verification links to multiple users, make separate API calls for each user.
  • You can return users to your own application when the flow ends by supplying completedUrl and incompleteUrl — see Redirecting users back to your application.
POSThttps://api.seon.io/orchestration-api/v1/workflow-link
Header x-api-key: Your SEON API key from Admin Panel / Settings / API Keys.
Regional endpoints: US https://api.us-east-1-main.seon.io/orchestration-api/v1/workflow-link · APAC https://api.ap-southeast-1-main.seon.io/orchestration-api/v1/workflow-link

The headers and all input fields are identical to the Workflow API. Workflow links additionally accept expiresIn, completedUrl and incompleteUrl:

workflowIdstring (uuid)required

The unique identifier of the workflow to execute. Obtain from Admin Panel / Workflows.

inputsobjectrequired

Workflow input parameters. user_id is always required; other required inputs depend on your workflow configuration.

34 child attributes
user_idstringrequired

Your user's unique identifier in your system. Always required.

emailstring (email)conditional

Full email address. Required if Email check is enabled in your workflow.

phone_numberstringconditional

Phone number with country code (max 19 chars). Required if Phone check is enabled.

max 19 chars
ipstring

User's IP address. Auto-captured from the browser if not provided.

user_fullnamestringconditional

User's full name. Required for AML check or Document verification if set to Sent with session trigger.

user_firstnamestringconditional

User's first name. Required for NIN/BVN eKYC checks.

user_middlenamestring

User's middle name.

user_lastnamestringconditional

User's last name. Required for NIN/BVN eKYC checks.

user_dobstring (date)conditional

Date of birth in YYYY-MM-DD format. Required for NIN/BVN/CURP/SSN eKYC checks.

reference_imagestring (uri)conditional

URL to a reference image for face match. Required if Face match is enabled and set to Sent with session trigger.

ninstringconditional

Nigerian National ID Number (11 digits). Required for NIN eKYC.

bvnstringconditional

Bank Verification Number (11 digits). Required for BVN eKYC.

cpfstringconditional

Brazilian tax identifier (format: 123.456.789-00). Required for CPF eKYC.

curpstringconditional

Mexican population ID (18 characters). Required for CURP eKYC.

ssnstringconditional

US Social Security Number (format: 123-45-6789). Required for SSN eKYC.

aadhaarstringconditional

Indian Aadhaar identifier (12 digits). Required for Aadhaar eKYC.

sessionstring

Device fingerprint. Auto-collected by the SDK.

device_idstring

Third-party device fingerprint ID.

user_pobstring

Place of birth.

user_photoid_numberstring

Photo ID number.

user_countrystring

ISO 3166-1 two-character country code.

user_citystring

City name.

user_regionstring

ISO 3166-2 two-character region code.

user_zipstring

Postal/zip code.

user_streetstring

Street address line 1.

user_street2string

Street address line 2.

user_addressstringconditional

Full address. Required for Address verification if set to Sent with session trigger.

user_genderstring

User gender.

user_creatednumber

User registration date (UNIX timestamp).

transaction_idstring

Unique transaction identifier.

transaction_typestring

Transaction type (e.g. purchase).

transaction_amountnumber

Transaction amount (decimal, e.g. 539.99).

transaction_currencystring

ISO 4217 currency code (e.g. USD).

custom_fieldsobject

Key-value pairs for custom data points.

expiresIninteger

Link lifetime in seconds. Minimum 3600 (1 hour), maximum 2592000 (30 days). Overrides the default 7-day expiration for this link only.

min 3600, max 2592000
completedUrlstring (uri)

Absolute URL the user is redirected to once the verification journey reaches a finished result.

incompleteUrlstring (uri)

Absolute URL the user is redirected to when the journey ends without reaching a finished result.

Your request should look like the following when using redirect URLs:

{
  "workflowId": "3f8c1e42-9b7a-4c25-8d61-0a2f5e7b9c14",
  "inputs": {
    "user_id": "user-12345",
    "email": "user@example.com"
  },
  "expiresIn": 172800,
  "completedUrl": "https://app.example.com/verification/complete",
  "incompleteUrl": "https://app.example.com/verification/incomplete"
}
curl -X POST "https://api.seon.io/orchestration-api/v1/workflow-link" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $SEON_API_KEY" \
  -d '{
    "workflowId": "3f8c1e42-9b7a-4c25-8d61-0a2f5e7b9c14",
    "inputs": {
      "user_id": "user-12345",
      "email": "user@example.com"
    },
    "expiresIn": 172800,
    "completedUrl": "https://app.example.com/verification/complete",
    "incompleteUrl": "https://app.example.com/verification/incomplete"
  }'
import os
import requests

response = requests.post(
    "https://api.seon.io/orchestration-api/v1/workflow-link",
    headers={
        "Content-Type": "application/json",
        "x-api-key": os.environ["SEON_API_KEY"],
    },
    json={
        "workflowId": "3f8c1e42-9b7a-4c25-8d61-0a2f5e7b9c14",
        "inputs": {"user_id": "user-12345", "email": "user@example.com"},
        "expiresIn": 172800,
        "completedUrl": "https://app.example.com/verification/complete",
        "incompleteUrl": "https://app.example.com/verification/incomplete",
    },
)

data = response.json()["data"]
print(data["redirectUrl"], data["executionId"])
const response = await fetch("https://api.seon.io/orchestration-api/v1/workflow-link", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.SEON_API_KEY,
  },
  body: JSON.stringify({
    workflowId: "3f8c1e42-9b7a-4c25-8d61-0a2f5e7b9c14",
    inputs: { user_id: "user-12345", email: "user@example.com" },
    expiresIn: 172800,
    completedUrl: "https://app.example.com/verification/complete",
    incompleteUrl: "https://app.example.com/verification/incomplete",
  }),
});

const { data } = await response.json();
console.log(data.redirectUrl, data.executionId);
<?php
$apiKey = getenv('SEON_API_KEY');

$payload = [
    'workflowId' => '3f8c1e42-9b7a-4c25-8d61-0a2f5e7b9c14',
    'inputs' => ['user_id' => 'user-12345', 'email' => 'user@example.com'],
    'expiresIn' => 172800,
    'completedUrl' => 'https://app.example.com/verification/complete',
    'incompleteUrl' => 'https://app.example.com/verification/incomplete',
];

$ch = curl_init('https://api.seon.io/orchestration-api/v1/workflow-link');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        "x-api-key: {$apiKey}",
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true)['data'];
echo $data['redirectUrl'] . "\n";
dataobjectrequired
2 child attributes
executionIdstring (uuid)required

Unique identifier for this workflow execution.

redirectUrlstring (uri)required

URL to redirect the end user to for completing the flow.

{
  "data": {
    "executionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "redirectUrl": "https://transfer.seonidv.com/?t=euw1:1e1f66b0-cc43-429d-ba69-096daca813fd"
  }
}

Use cases

The Workflow Link endpoint is ideal for scenarios where you want to send verification links directly to end users without requiring frontend SDK integration:

Use caseDescription
Email verification linksSend verification URLs via email campaigns or transactional emails.
SMS verification linksSend short verification URLs via SMS to mobile users.
Customer support workflowsGenerate links for support agents to send to customers for manual verification.
Asynchronous verificationAllow users to complete verification at their convenience without real-time session management.

Comparison with the init-workflow endpoint

FeatureInit Workflow (/v1/init-workflow)Workflow Link (/v1/workflow-link)
Responsetoken (for SDK)redirectUrl (shareable link)
SDK integrationRequiredNot required
Default expiration1 hour7 days
Return to your applicationHandled by your frontend via SDK eventscompletedUrl / incompleteUrl
Best forReal-time in-app verificationAsynchronous/off-platform verification

SEON hosted verification flow

When using the Workflow Link endpoint, the redirectUrl directs end users to a SEON-hosted Orchestration SDK frontend. This means:

  • No SDK integration required: you don't need to embed or configure the SEON SDK in your application.
  • Fully managed user experience: SEON hosts and maintains the verification UI, ensuring it's always up-to-date with the latest features and security updates.
  • Cross-platform compatibility: the hosted verification flow works on any desktop, mobile or tablet device with a modern web browser.

This approach is ideal when you want to offload the verification experience entirely to SEON, rather than embedding the SDK directly into your own web or mobile application.

Redirecting users back to your application

By default, users remain on the SEON-hosted result screen when the verification journey ends. If you supply redirect URLs when creating the link, users are returned to your application automatically instead.

AttributeWhen the user is redirectedTiming
completedUrlThe journey reaches a finished result.Once the verification journey reaches a finished result.
incompleteUrlThe journey ends without reaching a finished result: an error during the flow, expiry of the session or link, or the user choosing to quit.Immediately.

Both attributes are optional and independent — you can supply either, both, or neither. Each must be a valid absolute URL, and we recommend HTTPS.

Orchestration SDK

You can integrate SEON's Orchestration module directly into a web app by using our JavaScript SDK. Please use our npm-hosted package to ensure you always load the latest available version. Visit the SEON Orchestration SDK npm page to see the latest version and its changelog.

  1. Install the SDK via npm or yarn and import it into your application.
  2. Initialize a workflow from your backend using the Workflow API described above to get a token.
  3. Call SeonOrchestration.start(config) with the token to launch the verification flow.
  4. Listen to events (completed, error, cancelled) to handle the verification result.
  5. Use webhooks or the Admin Panel to access detailed verification results and captured media.

Installation

npm install @seontechnologies/seon-orchestration
# or
yarn add @seontechnologies/seon-orchestration
import { SeonOrchestration } from '@seontechnologies/seon-orchestration';

Prerequisites

  • Node.js >= 20.0.0, npm >= 7.0.0
  • SEON account with workflow access
  • API key (obtain from Admin Panel / Settings / API Keys)
  • At least one workflow created (Admin Panel / Workflows)

Browser compatibility

BrowserMin version
Chrome96
Safari15
Firefox79
Opera82
iOS Safari15
Android Browser81
Chrome for Android96
Firefox for Android79
Internet ExplorerNot supported

Configuration parameters

To configure the Orchestration SDK, create a config object and pass it to SeonOrchestration.start(config).

tokenstringrequired

JWT token obtained from your backend via the Workflow API.

languagestring

UI language: en English, de German, es Spanish, fr French, it Italian, pt Portuguese, hu Hungarian, ar Arabic (UAE), zh Chinese. If not specified or unsupported, the SDK falls back to English.

One of en, de, es, fr, it, pt, hu, ar, zh · Default en
themeobject

Custom theming configuration. Accessibility: ensure colour contrast meets WCAG 2.1 AA (4.5:1 for normal text).

5 child attributes
lightobject

Colour scheme for one mode (light or dark).

5 child attributes
baseTextOnLightstring

Text colour on light backgrounds. Min 4.5:1 ratio.

baseTextOnDarkstring

Text colour on dark backgrounds. Min 4.5:1 ratio.

baseAccentstring

Primary accent/brand colour.

baseOnAccentstring

Text colour on accent backgrounds. Min 4.5:1 against baseAccent.

logoUrlstring (uri)

URL to a custom logo image (SVG preferred; PNG/JPEG supported).

darkobject

Colour scheme for one mode (light or dark).

5 child attributes
baseTextOnLightstring

Text colour on light backgrounds. Min 4.5:1 ratio.

baseTextOnDarkstring

Text colour on dark backgrounds. Min 4.5:1 ratio.

baseAccentstring

Primary accent/brand colour.

baseOnAccentstring

Text colour on accent backgrounds. Min 4.5:1 against baseAccent.

logoUrlstring (uri)

URL to a custom logo image (SVG preferred; PNG/JPEG supported).

fontFamilystring

Custom font family name (e.g. Inter).

fontUrlstring (uri)

URL to load the custom font from (Google Fonts URLs only, WOFF2 recommended).

fontWeightstring

Font weight (e.g. 400, 500, 600).

renderingModestring

How the SDK renders. fullscreen takes over the entire viewport — best for mobile web and single-purpose flows. popup opens a new browser window — desktop apps where the main UI should stay visible. inline renders inside a container element — embedded within your existing page layout.

One of fullscreen, inline, popup · Default fullscreen
containerIdstringconditional

DOM element ID for the SDK container. Required when renderingMode is inline.

Core methods

MethodDescription
SeonOrchestration.start(config)Start the verification flow with the provided configuration
SeonOrchestration.close()Close the current verification flow and clean up the UI
SeonOrchestration.on(event, handler)Subscribe to SDK events
SeonOrchestration.off(event, handler)Unsubscribe from SDK events

Events

EventCallback signatureDescription
opened() => voidFlow UI opened
closed() => voidFlow UI closed
started() => voidVerification started
completed(status: CompletionTypes) => voidVerification completed
cancelled() => voidUser cancelled
error(errorCode: ErrorCodes) => voidError occurred

Completion types: success, pending, failed, unknown

Error codes

Error codes received via the error event:

CodeDescription
error_code_1Device not supported — no capable camera/device found, or general error screen dismissed
error_code_3Authentication failed — unauthorized request (invalid/expired token)
error_code_4Document capture SDK error — failed to initialize document scanning
error_code_5Document capture retry limit exceeded — user exceeded max retries for document scanning
error_code_6Liveness check retry limit exceeded — user exceeded max retries for liveness detection
unknownUnhandled error — unexpected error or unhandled promise rejection

SDK exceptions

Exceptions thrown by SeonOrchestration.start() (catch via try/catch):

Error messageCause
IDV flow is already running.Calling start() when a flow is already active
Configuration is not set.Calling start() without passing config
Failed to initialize client: {status} {statusText}Backend init failed (e.g. invalid/expired token)
Invalid response from client init.Invalid account configuration
Container ID is required for inline rendering.Using renderingMode: 'inline' without containerId
Container element with id '{id}' not found.Container DOM element doesn't exist
Failed to open popup window. Please allow popups and try again.Browser blocked the popup window
Invalid rendering mode specified.Invalid renderingMode value

Example: minimal integration

import { SeonOrchestration } from '@seontechnologies/seon-orchestration';

// 1. Get token from YOUR backend (keeps API keys secure)
const { token } = await fetch('/api/init-verification', { method: 'POST' })
  .then(r => r.json());

// 2. Start verification
await SeonOrchestration.start({ token, language: 'en' });

Example: full configuration

// On page load: set up event listeners
SeonOrchestration.on('completed', (status) => {
  console.log('Verification completed:', status);
});
SeonOrchestration.on('error', (errorCode) => {
  console.error('Verification error:', errorCode);
});

const config = {
  token: 'eyJhbGciOiJIUzI1NiIs...', // from your backend
  language: 'en',
  renderingMode: 'fullscreen',
  theme: {
    light: {
      baseTextOnLight: '#1a1a1a',
      baseTextOnDark: '#ffffff',
      baseAccent: '#0066cc',
      baseOnAccent: '#ffffff',
      logoUrl: 'https://example.com/logo-dark.svg'
    },
    dark: {
      baseTextOnLight: '#e5e5e5',
      baseTextOnDark: '#1a1a1a',
      baseAccent: '#4d9fff',
      baseOnAccent: '#000000',
      logoUrl: 'https://example.com/logo-light.svg'
    },
    fontFamily: 'Inter',
    fontUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap',
    fontWeight: '500'
  }
};

await SeonOrchestration.start(config);

Example: inline rendering

<!-- In your HTML -->
<div id="verification-container" style="width: 100%; min-height: 600px;"></div>
await SeonOrchestration.start({
  token,
  renderingMode: 'inline',
  containerId: 'verification-container'
});
RequirementDetails
Container elementMust exist in the DOM before start() is called
Minimum size400×600 px recommended for usability
ResponsiveContainer should be responsive; the SDK adapts to the available space

Example: React integration

import React, { useEffect, useState } from 'react';
import { SeonOrchestration, CompletionTypes, ErrorCodes } from '@seontechnologies/seon-orchestration';

export function VerificationComponent({ userId, onComplete, onError }) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const handleCompleted = (status: CompletionTypes) => {
      onComplete(status);
    };
    const handleError = (errorCode: ErrorCodes) => {
      setError(`Error: ${errorCode}`);
      onError(errorCode);
    };
    const handleClosed = () => setIsLoading(false);

    SeonOrchestration.on('completed', handleCompleted);
    SeonOrchestration.on('error', handleError);
    SeonOrchestration.on('closed', handleClosed);
    return () => {
      SeonOrchestration.off('completed', handleCompleted);
      SeonOrchestration.off('error', handleError);
      SeonOrchestration.off('closed', handleClosed);
    };
  }, [onComplete, onError]);

  const startVerification = async () => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch('/api/init-verification', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ userId }),
      });
      const { token } = await response.json();
      await SeonOrchestration.start({ token, language: 'en' });
    } catch (err) {
      setError(err.message);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <button onClick={startVerification} disabled={isLoading}>
        {isLoading ? 'Starting...' : 'Start Verification'}
      </button>
    </div>
  );
}