> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withverifa.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withverifa.com/_mcp/server.

# Migrating from Persona

> Drop-in replacement for Persona's JS embed widget, webhook layer, and inquiry model — without re-architecting your backend.

If you're integrating Verifa and coming from Persona, this page maps the
Persona concepts you already know to the Verifa equivalents. The two
platforms are structurally similar enough that most migrations are a string
swap rather than a refactor.

## TL;DR

* Persona's **Inquiry** → Verifa's **Session**
* Persona's **Template** → Verifa's **Workflow**
* `Persona.Client({...})` → `Verifa.Client({...})` (same constructor shape, same callbacks)
* Webhook event names + HMAC scheme are functionally equivalent
* Verifa is per-session billed, no per-check or per-report multiplication

You can keep your existing backend session-create / webhook handler
architecture; only the JS snippet and a handful of API field names change.

---

## Concept mapping

| Persona                                            | Verifa                                                          | Notes                                                                                                                                                                                                           |
| -------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Inquiry** (`inq_*`)                              | **Session** (`ses_*` / `session_*`)                             | The unit of verification. Created with one API call, the user completes it, you fetch results or wait for webhook.                                                                                              |
| **Template** (`itmpl_*` / `tmpl_*`)                | **Workflow** (`wf_*`)                                           | The rules + steps the user goes through. Identical concept, different label.                                                                                                                                    |
| **Account** (the person being verified)            | **Identity** (`ident_*`)                                        | A canonical person record that can have multiple Sessions over time.                                                                                                                                            |
| **Report** (per-check billing line)                | (no equivalent)                                                 | Verifa bills per Session, not per check. The same Session can include document + face match + AML screen + watchlist + risk scoring without per-check inflation.                                                |
| **Inquiry → Verification → Report** hierarchy      | **Session → Check**                                             | Verifa flattens Verification + Report into Check rows on the Session.                                                                                                                                           |
| `inquiryId` (server-created, single-use)           | `captureUrl` or `sessionToken` (server-created, single-session) | The frontend handle. Like Persona's `inquiry session token`, Verifa's `sessionToken` is a short-lived JWT (30 min) issued by `POST /sessions/:id/client-token` so the long-lived URL never reaches the browser. |
| `inquiry session token` (`POST /inquiry-sessions`) | `client_token` (`POST /sessions/:id/client-token`)              | Same idea — short-lived embed token. Verifa's TTL is 30 minutes by default.                                                                                                                                     |
| `templateId` (public, frontend-safe)               | `publishableKey` + `templateId`                                 | Persona's templateId is implicitly tied to your account; Verifa explicitly separates the public key from the workflow.                                                                                          |

## JS widget migration

### Before — Persona

```html
<script src="https://cdn.withpersona.com/dist/persona-v5.x.js"></script>
<script>
  const client = new Persona.Client({
    templateId:   'itmpl_abc123',
    environmentId: 'env_abc',
    referenceId:  user.id,
    onReady:    () => client.open(),
    onComplete: ({ inquiryId, status, fields }) => save(inquiryId),
    onCancel:   ({ inquiryId, sessionToken }) => closed(),
    onError:    (error) => console.error(error),
  });
  client.open();
</script>
```

### After — Verifa

```html
<script
  src="https://cdn.withverifa.com/dist/verifa-v1.0.0.js"
  integrity="sha384-/2DZ+OBwe8NRgYq4X0/qpvx7e7s9CjFy0K7WZX4E5utplz4njxn3A0tNTXNsK4fm"
  crossorigin="anonymous"
></script>
<script>
  // Your backend already created a session and returned capture_url.
  new Verifa.Client({
    captureUrl: session.capture_url,
    onComplete: ({ sessionId, status }) => save(sessionId),
    onCancel:   ({ sessionId }) => closed(),
    onError:    ({ code, message }) => console.error(code, message),
  }).open();
</script>
```

**Differences:**

* Verifa's recommended path is `captureUrl` (server-created), matching Persona's
  *production* mode with `inquiryId`. Persona's `templateId`-only quickstart
  maps to Verifa's `publishableKey` + `templateId` (also supported — see
  [JavaScript SDK → Quickstart](/integration-guides/java-script-sdk)).
* `onReady` is optional. The SDK opens automatically on `.open()`; you don't
  need an `onReady → open()` pattern like Persona's.
* `onComplete` payload is `{ sessionId, status }`. Field-level results live
  server-side (fetch via `GET /api/v1/sessions/:id`); they're never delivered
  to the browser, matching Persona's "no PII in the browser" stance.
* `onError` receives `{ code, message }` with **string codes** you can switch
  on. See [the error reference](/integration-guides/java-script-sdk#error-code-reference).

## Backend migration

### Creating a Session (Persona's `POST /api/v1/inquiries`)

**Persona:**

```bash
curl -X POST https://withpersona.com/api/v1/inquiries \
  -H "Authorization: Bearer persona_secret_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "attributes": {
        "inquiry-template-id": "itmpl_abc123",
        "reference-id": "user_42",
        "fields": { "name-first": "Jane" }
      }
    }
  }'
```

**Verifa:**

```bash
curl -X POST https://api.withverifa.com/api/v1/sessions \
  -H "X-API-Key: vk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow_id": "wf_abc123",
    "external_ref": "user_42",
    "country": "US"
  }'
```

| Persona field                            | Verifa field                                                                      |
| ---------------------------------------- | --------------------------------------------------------------------------------- |
| `data.attributes.inquiry-template-id`    | `workflow_id`                                                                     |
| `data.attributes.reference-id`           | `external_ref`                                                                    |
| `data.attributes.fields.name-first` etc. | `prefill.first_name` etc. (set via `/sessions/:id/prefill` or in the create body) |
| `data.attributes.redirect-uri`           | `redirect_url`                                                                    |
| `data.attributes.locale`                 | `locale`                                                                          |
| `data.attributes.theme-id`               | (handled per-workflow; no per-session override)                                   |

Response shape differs slightly — Persona returns JSON:API style with
`data.id` and `data.attributes.fields-as-array`. Verifa returns flat JSON
with `id`, `capture_url`, `status`, etc.

### Fetching results

**Persona:**

```bash
curl https://withpersona.com/api/v1/inquiries/inq_xxx \
  -H "Authorization: Bearer persona_secret_xxx"
```

**Verifa:**

```bash
curl https://api.withverifa.com/api/v1/sessions/session_xxx \
  -H "X-API-Key: vk_live_xxx"
```

Both return the structured result plus all the per-check fields. The Verifa
response is flatter (no JSON:API envelope).

### Webhooks

Both platforms sign with HMAC-SHA256. Verifa's verification snippet:

```python
import hmac, hashlib

# X-Verifa-Signature header on every webhook delivery
def verify(payload_bytes: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)
```

**Event-name mapping:**

| Persona event       | Verifa event                                              |
| ------------------- | --------------------------------------------------------- |
| `inquiry.created`   | `session.created`                                         |
| `inquiry.started`   | `session.started`                                         |
| `inquiry.completed` | `session.completed`                                       |
| `inquiry.approved`  | (covered by `session.completed` with `result: passed`)    |
| `inquiry.declined`  | (covered by `session.completed` with `result: failed`)    |
| `inquiry.expired`   | `session.expired`                                         |
| `report.created`    | `check.created`                                           |
| `report.run`        | `check.completed`                                         |
| `case.*`            | `case.*` (same name, slightly different lifecycle states) |

Full Verifa event list in the [Webhooks Guide](/integration-guides/webhooks-guide).

## Pricing model

Persona bills per **report**. A single Inquiry that runs document verification

* database checks + selfie + government ID + watchlist screening can produce
  4-5 line items, each billed separately. Reattempts re-charge.

Verifa bills per **session**. The same multi-check session is one charge;
reattempts within a session don't re-bill. AML screening + 12 months of
monitoring is a separate annual line item.

If you're migrating from Persona for cost reasons, expect \~30-50% lower
all-in cost on equivalent volume, plus removal of the "per-check budget
forecast" mental overhead.

## Differences worth knowing

* **No JSON:API envelope.** Verifa's responses are plain `{ id, ... }`
  objects. If you have JSON:API parsing helpers, drop them.
* **Webhooks have idempotent delivery via event IDs.** Persona doesn't
  publish a stable retry-once contract; Verifa does (see
  [Idempotency](/api-details/idempotency)).
* **No Persona-Connect equivalent yet** (reusable cross-org identity). On
  the roadmap.
* **Native mobile SDKs.** Persona has iOS + Android SDK; Verifa has a
  React Native SDK shipping now, iOS/Android coming. If your integration
  is mobile-first, talk to us about timing.
* **Field names are snake_case by default**, with opt-in `camelCase` via
  the `Verifa-Key-Inflection` header. Persona uses `kebab-case`. The shift
  is mostly cosmetic but check your JSON parsers.
* **No template-versioning UI yet.** Workflow edits are immediate on the
  workflow; for staged rollout, clone the workflow + flip your
  `workflow_id` in code.

## Help

If you hit something during migration that this page doesn't cover, email
[support@withverifa.com](mailto:support@withverifa.com) with your Persona inquiry template and a description
of what you're trying to reproduce. We've done enough Persona migrations to
have most edge cases mapped.

## Related

* [JavaScript SDK](/integration-guides/java-script-sdk) — Full Verifa.Client reference
* [Webhooks Guide](/integration-guides/webhooks-guide) — Event payloads + HMAC verification
* [Choosing an Integration Method](/getting-started/choosing-an-integration-method) — Compare integration options
* [Authentication](/api-details/authentication) — Key types and scopes