# 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 this endpoint to start a workflow
session and receive a token for the frontend SDK.

For more context on how to begin your API integration check the Introduction
section or the [Integration Guide](../integration/orchestration.html).

### Good to know

- The `workflowId` must be a valid UUID of an active workflow created in the
  Admin Panel (Admin Panel / [Workflows](https://admin.seon.io/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.

{% callout type="warning" title="Security" %}
Never expose your API key in frontend code. Always initialize workflows from
your backend server.
{% /callout %}

{% callout type="note" title="US and APAC environments" %}
If you are using our US or APAC Transaction Processing Environment please
contact SEON for access and use the regional endpoint listed under each
operation below.
{% /callout %}

### Common Workflow Scenarios

| Workflow type | Required 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 check | `user_id`, `email`, `phone_number` |
| Full fraud check (Email + Phone + IP) | `user_id`, `email`, `phone_number` (IP auto-captured) |
| AML screening | `user_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

{% endpoint operation="initWorkflow" /%}

### Request attributes

{% attributes operation="initWorkflow" /%}

### Code samples

{% code-samples operation="initWorkflow" /%}

## Response

The endpoint returns a JSON structured response.

{% attributes operation="initWorkflow" of="response" /%}

{% example operation="initWorkflow" of="response" /%}

## Error Responses

Both endpoints on this page return the same error responses.

{% errors operation="initWorkflow" /%}

## Shareable Workflow Links

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.

### Good to know about workflow links

- 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](#redirecting-users-back-to-your-application).

### Workflow link request

{% endpoint operation="createWorkflowLink" /%}

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

{% attributes operation="createWorkflowLink" /%}

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

{% example operation="createWorkflowLink" of="request" /%}

{% code-samples operation="createWorkflowLink" /%}

### Workflow link response

{% attributes operation="createWorkflowLink" of="response" /%}

{% example operation="createWorkflowLink" of="response" /%}

### 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 case | Description |
|---|---|
| Email verification links | Send verification URLs via email campaigns or transactional emails. |
| SMS verification links | Send short verification URLs via SMS to mobile users. |
| Customer support workflows | Generate links for support agents to send to customers for manual verification. |
| Asynchronous verification | Allow users to complete verification at their convenience without real-time session management. |

### Comparison with the init-workflow endpoint

| Feature | Init Workflow (`/v1/init-workflow`) | Workflow Link (`/v1/workflow-link`) |
|---|---|---|
| Response | `token` (for SDK) | `redirectUrl` (shareable link) |
| SDK integration | Required | Not required |
| Default expiration | 1 hour | 7 days |
| Return to your application | Handled by your frontend via SDK events | `completedUrl` / `incompleteUrl` |
| Best for | Real-time in-app verification | Asynchronous/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.

| Attribute | When the user is redirected | Timing |
|---|---|---|
| `completedUrl` | The journey reaches a finished result. | Once the verification journey reaches a finished result. |
| `incompleteUrl` | The 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.

{% callout type="important" title="Landing pages must not infer the verification outcome" %}
There is a single `completedUrl` for all finished results, so Approve, Review
and Reject all return the user to the same page. This is deliberate: the
decision is never exposed to the browser, where it could be read or manipulated
by the end user. Design your landing page as a neutral confirmation — for
example "Thanks, we're reviewing your submission" — and reflect the actual
status only after you receive the `workflow_execution_finished` webhook.
{% /callout %}

## 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](https://www.npmjs.com/package/@seontechnologies/seon-orchestration)
to see the latest version and its changelog.

{% callout type="tip" %}
The Orchestration SDK handles document verification, selfie checks, device
fingerprinting, and more in a single orchestrated flow. Initialize workflows
from your backend, then pass the token to this frontend SDK.
{% /callout %}

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.

{% callout type="note" %}
The specific checks performed depend on your workflow configuration in the
Admin Panel.
{% /callout %}

### Installation

```bash
npm install @seontechnologies/seon-orchestration
# or
yarn add @seontechnologies/seon-orchestration
```

```js
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

| Browser | Min version |
|---|---|
| Chrome | 96 |
| Safari | 15 |
| Firefox | 79 |
| Opera | 82 |
| iOS Safari | 15 |
| Android Browser | 81 |
| Chrome for Android | 96 |
| Firefox for Android | 79 |
| Internet Explorer | Not supported |

### Configuration parameters

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

{% attributes schema="OrchestrationSdkConfig" /%}

### Core methods

| Method | Description |
|---|---|
| `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

| Event | Callback signature | Description |
|---|---|---|
| `opened` | `() => void` | Flow UI opened |
| `closed` | `() => void` | Flow UI closed |
| `started` | `() => void` | Verification started |
| `completed` | `(status: CompletionTypes) => void` | Verification completed |
| `cancelled` | `() => void` | User cancelled |
| `error` | `(errorCode: ErrorCodes) => void` | Error occurred |

**Completion types:** `success`, `pending`, `failed`, `unknown`

### Error codes

Error codes received via the `error` event:

| Code | Description |
|---|---|
| `error_code_1` | **Device not supported** — no capable camera/device found, or general error screen dismissed |
| `error_code_3` | **Authentication failed** — unauthorized request (invalid/expired token) |
| `error_code_4` | **Document capture SDK error** — failed to initialize document scanning |
| `error_code_5` | **Document capture retry limit exceeded** — user exceeded max retries for document scanning |
| `error_code_6` | **Liveness check retry limit exceeded** — user exceeded max retries for liveness detection |
| `unknown` | **Unhandled error** — unexpected error or unhandled promise rejection |

### SDK exceptions

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

| Error message | Cause |
|---|---|
| `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

```js
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

```js
// 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

```html
<!-- In your HTML -->
<div id="verification-container" style="width: 100%; min-height: 600px;"></div>
```

```js
await SeonOrchestration.start({
  token,
  renderingMode: 'inline',
  containerId: 'verification-container'
});
```

| Requirement | Details |
|---|---|
| Container element | Must exist in the DOM before `start()` is called |
| Minimum size | 400×600 px recommended for usability |
| Responsive | Container should be responsive; the SDK adapts to the available space |

### Example: React integration

```tsx
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>
  );
}
```

{% callout type="warning" title="Security" %}
Never expose your API key in frontend code. Always initialize workflows from
your backend server using the Workflow API, then pass the returned token to
this SDK.
{% /callout %}
