For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
  • Getting Started
    • Introduction
    • How Verifa Works
    • Quickstart
    • Choosing an Integration Method
  • Use Cases
    • KYC Onboarding
    • Age Verification
    • AML Compliance
    • Fraud Prevention
    • Marketplace Trust & Safety
  • Core Concepts
    • Overview
    • Sessions
    • Verifications & Checks
    • Workflows
    • Identities
    • Cases
    • Screening & Reports
    • Lists
  • Integration Guides
    • Overview
    • JavaScript SDK
    • Web Capture Flow
    • API-Only Integration
    • Mobile SDK
    • Webhooks Guide
    • MCP Server
    • Migrating from Persona
  • API Details
    • Overview
    • Authentication
    • Pagination
    • Rate Limiting
    • Versioning
    • Errors
    • Webhooks
    • Idempotency
    • Key Inflection
    • Data Access
    • Data Retention
  • Tutorials
    • Creating Your First Verification Session
    • Creating a Workflow
    • Receiving Webhooks & Validating Signatures
    • Handling Webhook Events
    • Custom Document Types & AI Extraction
  • Best Practices
    • Testing
    • Preventing Duplicates
    • Fraud Signals
    • Changelog
  • API Reference
On this page
  • TL;DR
  • Concept mapping
  • JS widget migration
  • Before — Persona
  • After — Verifa
  • Backend migration
  • Creating a Session (Persona’s POST /api/v1/inquiries)
  • Fetching results
  • Webhooks
  • Pricing model
  • Differences worth knowing
  • Help
  • Related
Integration Guides

Migrating from Persona

Was this page helpful?
Previous

API Details

Next
Built with

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

PersonaVerifaNotes
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 hierarchySession → CheckVerifa 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 + templateIdPersona’s templateId is implicitly tied to your account; Verifa explicitly separates the public key from the workflow.

JS widget migration

Before — Persona

1<script src="https://cdn.withpersona.com/dist/persona-v5.x.js"></script>
2<script>
3 const client = new Persona.Client({
4 templateId: 'itmpl_abc123',
5 environmentId: 'env_abc',
6 referenceId: user.id,
7 onReady: () => client.open(),
8 onComplete: ({ inquiryId, status, fields }) => save(inquiryId),
9 onCancel: ({ inquiryId, sessionToken }) => closed(),
10 onError: (error) => console.error(error),
11 });
12 client.open();
13</script>

After — Verifa

1<script
2 src="https://cdn.withverifa.com/dist/verifa-v1.0.0.js"
3 integrity="sha384-/2DZ+OBwe8NRgYq4X0/qpvx7e7s9CjFy0K7WZX4E5utplz4njxn3A0tNTXNsK4fm"
4 crossorigin="anonymous"
5></script>
6<script>
7 // Your backend already created a session and returned capture_url.
8 new Verifa.Client({
9 captureUrl: session.capture_url,
10 onComplete: ({ sessionId, status }) => save(sessionId),
11 onCancel: ({ sessionId }) => closed(),
12 onError: ({ code, message }) => console.error(code, message),
13 }).open();
14</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).
  • 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.

Backend migration

Creating a Session (Persona’s POST /api/v1/inquiries)

Persona:

$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:

$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 fieldVerifa field
data.attributes.inquiry-template-idworkflow_id
data.attributes.reference-idexternal_ref
data.attributes.fields.name-first etc.prefill.first_name etc. (set via /sessions/:id/prefill or in the create body)
data.attributes.redirect-uriredirect_url
data.attributes.localelocale
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:

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

Verifa:

$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:

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

Event-name mapping:

Persona eventVerifa event
inquiry.createdsession.created
inquiry.startedsession.started
inquiry.completedsession.completed
inquiry.approved(covered by session.completed with result: passed)
inquiry.declined(covered by session.completed with result: failed)
inquiry.expiredsession.expired
report.createdcheck.created
report.runcheck.completed
case.*case.* (same name, slightly different lifecycle states)

Full Verifa event list in the 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).
  • 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 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 — Full Verifa.Client reference
  • Webhooks Guide — Event payloads + HMAC verification
  • Choosing an Integration Method — Compare integration options
  • Authentication — Key types and scopes