Identity verification
Check verification status, trigger Tax, Banking, and Enhanced verification, find missing fields, and run an embedded identity verification session in the V3 API.
This page shows you how to check and run identity verification for an Account (KYB for a business, KYC for an individual) and for a Person. It covers the onboarding status read, triggering verification, finding what's missing, running an interactive identity session, and recording agreement acceptances.
This page describes how the API behaves. It doesn't describe what any regulation requires of you, and a Verified result isn't a legal determination. This isn't legal advice. Consult a qualified professional about your own obligations.
Who runs verification
Verification is about the Account being verified, so most calls run as that Account:
- A contractor completing their own onboarding calls these endpoints with their own session.
- A platform that manages child Accounts can call them with
X-Wingspan-Account: <childAccountId>. See Acting on behalf of Accounts. - A payer can't run verification on a Payee's Account it doesn't have access to. Payers see verification results through requirements such as
TaxVerification.
A verification belongs to the Account being verified, not to the payer that asked for it. Verifying a ComplianceEntity with reusePolicy: ResolveExisting (the default) returns a recent passing result instead of running the check again, so a contractor who works with several payers doesn't have to repeat it for each one.
Verification levels
| Level | What it covers, in general terms |
|---|---|
Tax | The tax identity: name and taxpayer ID on the Account's ComplianceEntity. |
Banking | The checks needed for banking features, which build on Tax. |
Enhanced | Adds an interactive identity step, such as a document scan and selfie. |
Each level reports one of these statuses:
| Status | Meaning |
|---|---|
None | Not started. |
Pending | In progress. |
Verified | Passed. |
Failed | Didn't pass. |
UpdateRequired | More or corrected information is needed. Check missing fields. |
Check onboarding status
GET /v3/onboarding/status returns the verification levels, a requirements summary, and an overall isEligible for the Account.
curl https://api.wingspan.app/v3/onboarding/status \
-H "Authorization: Bearer $WINGSPAN_TOKEN"// 200 OK (trimmed)
{
"accountId": "qY0t19FmElxN3ur1EiW2Ov",
"country": "US",
"isEligible": false,
"verifications": {
"tax": "Pending",
"banking": "None",
"enhanced": "None"
},
"requirements": { "total": 4, "complete": 1, "incomplete": 3, "blocking": 2 }
}isEligibleistruewhen the Account has completed every required onboarding step.requirements.blockingcounts incomplete requirements that block payments.- If the requirements summary is temporarily unavailable, the response leaves out
requirementsand reportsisEligible: false. Don't treat that as a permanent answer; read it again later. verifications.profilesappears when the Account has verification profiles for more than one country. Only the homecountryaffectsisEligible.
Trigger verification
POST /v3/onboarding/verify starts verification. Send a level, or omit it to check every level that applies.
curl -X POST https://api.wingspan.app/v3/onboarding/verify \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "level": "Tax" }'// 200 OK
{
"accountId": "qY0t19FmElxN3ur1EiW2Ov",
"wasSuccessful": true,
"verifications": { "tax": "Pending", "banking": "None", "enhanced": "None" }
}wasSuccessful means verification started, not that it passed. Read GET /v3/onboarding/status to follow progress.
If verification can't start, you get 422 ValidationError. On this endpoint you may branch on these detailCode values (and fall back to code for anything else):
detailCode | What to do |
|---|---|
users.OnboardingVerificationProfileNotFound | The Account has no tax identity yet. Create a ComplianceEntity first. |
users.OnboardingVerificationFieldsMissing | Required information is missing. errors[] names the fields and extensions.requiredDocuments lists documents. Fill them in and try again. |
users.OnboardingAccountPrincipalUnbound | The Account has no principal person set. Contact support. |
You can also verify a specific ComplianceEntity with POST /v3/platform/compliance-entities/{complianceEntityId}/verify. The body takes a level (Tax, Banking, or Enhanced) and an optional reusePolicy: ResolveExisting (the default) returns a recent passing result without running the check again, and ForceNew runs a fresh check. Results appear on the ComplianceEntity's verificationStatus, grouped by country, with one lane per check kind (for example Tax, Identity, Sanctions), each NotApplicable, NotStarted, Pending, Verified, Failed, or Expired.
Find missing fields
GET /v3/onboarding/missing-fields tells you what's still needed. It never runs verification.
curl -G https://api.wingspan.app/v3/onboarding/missing-fields \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
--data-urlencode "level=Tax"// 200 OK (trimmed)
{
"accountId": "qY0t19FmElxN3ur1EiW2Ov",
"level": "Tax",
"requiredFields": ["taxIdentifiers"],
"requiredDocuments": []
}- Without
level, the response combines every level. - With a
level, the response may includecompleteOnboardingToken, a short-lived token that opens Wingspan's embedded flow so the person can finish an interactive step. - Before an Account has a tax identity, a level-scoped read returns empty arrays. Empty doesn't mean verified; check
GET /v3/onboarding/status.
A typical loop: read missing fields, collect the values in your UI, update the ComplianceEntity, trigger verification, then read status.
Run an identity verification session
An IdentityVerification is an interactive session, such as a document scan and selfie, that the person completes inside your app through the verification provider's SDK.
POST /v3/onboarding/identity-verifications takes only the subject to verify: a Person or an Account.
curl -X POST https://api.wingspan.app/v3/onboarding/identity-verifications \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "subject": { "type": "Person", "id": "2MRBaewOMAak9Oyf9dFVBa" } }'// 201 Created (trimmed)
{
"id": "_4R4JqfhH7LZ8yDcaiRwy0",
"subject": { "type": "Person", "id": "2MRBaewOMAak9Oyf9dFVBa" },
"tier": "Enhanced",
"status": "Created",
"consumptionMode": "EmbeddedIframe",
"events": { "createdAt": "2026-09-24T16:30:00Z" }
}Today this always runs the enhanced embedded flow. Tier selection, extra checks, metadata, and notification options aren't accepted yet.
This endpoint runs as a Person, so don't send X-Wingspan-Account; it returns 400 AccountScopeNotApplicable. Replaying the same Idempotency-Key with the same subject returns the original verification. The same key with a different subject returns 409.
Show the session
consumptionMode: EmbeddedIframe means the person completes the session in the provider's SDK, embedded in your app. To start or continue it, call POST /v3/onboarding/identity-verifications/{identityVerificationId}/resume. The response carries a short-lived sessionToken and, when the provider supplies one, sessionTokenExpiresAt.
curl -X POST https://api.wingspan.app/v3/onboarding/identity-verifications/_4R4JqfhH7LZ8yDcaiRwy0/resume \
-H "Authorization: Bearer $WINGSPAN_TOKEN"// 200 OK (trimmed)
{
"id": "_4R4JqfhH7LZ8yDcaiRwy0",
"status": "Created",
"consumptionMode": "EmbeddedIframe",
"sessionToken": "<short-lived provider token>",
"sessionTokenExpiresAt": "2026-09-24T16:35:00Z"
}Pass sessionToken to the provider SDK in your app. It isn't a URL, so don't load it in an iframe. Each resume call issues a new token, and only resume returns it: GET and list responses never include it. Don't store it. Call resume again when you need a fresh one. Contact support for the SDK setup for your account.
sessionUrl is reserved for providers that supply a hosted verification URL. SDK-based sessions leave it empty.
Read the result
GET /v3/onboarding/identity-verifications/{identityVerificationId}:
status | Meaning |
|---|---|
Created | Session created, not started. |
InProgress | The person has started. |
UpdateRequired | The person needs to provide corrected or additional information. |
Completed | Finished. Read decision.outcome for the result. |
Failed | A system error stopped processing. This isn't a denial. |
Cancelled | Stopped. |
Expired | The session lapsed. |
When status is Completed, decision.outcome is one of Verified or Approved (both mean it passed), Denied, ManualReview (Wingspan is reviewing it), or UpdateRequired. decision.reasons[] lists reason codes. checks[] gives the per-check breakdown.
List verifications with GET /v3/onboarding/identity-verifications, filtered by filter[subjectType][eq] and filter[subjectId][eq].
Cancelling a verification and the separate per-check endpoints (.../checks) aren't available in the V3 API yet. The per-check data is on the verification's checks[] array.
Business owners and representatives
For a business Account, the owners and the person who represents the business are recorded as Stakeholders on the Account, using ownershipPercentage, isController, and isPrincipal. Manage them with POST /v3/platform/accounts/{accountId}/stakeholders. To let an owner enter their own details, invite them with POST /v3/platform/accounts/{accountId}/stakeholders/{stakeholderId}/invite. See Team access and roles.
Record agreement acceptance
Some onboarding steps need the person to accept an agreement. POST /v3/onboarding/acknowledgements/{acknowledgementName} records acceptance of a specific version. Wingspan stores the version, time, and IP address.
curl -X POST https://api.wingspan.app/v3/onboarding/acknowledgements/WingspanTosAcceptance \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "version": "2026-03-01" }'GET /v3/onboarding/acknowledgements/{acknowledgementName}returnsacknowledgementStatus:NotGiven,Given, orRevoked.POST .../{acknowledgementName}/revokerecords that the person declined or withdrew, with theversion.
Names include WingspanTosAcceptance, WingspanPrivacyPolicyAcceptance, ElectronicDisclosureAndConsent, ElectronicTaxFormConsent, W9Certification, W8BenCertification, DepositAccountHolderAgreement, and DebitCardHolderAgreement. The reference page lists them all.
Webhooks
IdentityVerification.* events aren't available for subscription yet. Poll GET /v3/onboarding/status or the verification itself, pacing your requests to stay within rate limits.
ComplianceEntity.Created and ComplianceEntity.Updated are subscribable. They tell you a tax identity was added or changed, not that a verification finished. See Tax information.
Common mistakes
- Treating
wasSuccessful: trueas a pass. It means verification started. - Treating an empty missing-fields response as done. Before a tax identity exists, the arrays are empty. Check status.
- Sending
X-Wingspan-Accountto create an identity verification. That endpoint runs as a Person and rejects the header. - Reading
Failedon a session as a denial.Failedis a system error. A denial isstatus: Completedwithdecision.outcome: Denied. - Creating a new session every time the page reloads. Reuse the verification and call
resumefor a freshsessionToken. - Loading
sessionTokenas a URL. It's a token for the provider SDK.
Related
Updated 10 days ago