Employment eligibility verification (Form I-9)
Collect a Payee's Form I-9, verify their documents, and run the E-Verify case from the API.
An employment eligibility verification is a Payee's Form I-9 for one payer. The Payee completes Section 1 and submits it. The payer records the documents it examined, then signs Section 2. If the payer is enrolled in E-Verify, the E-Verify case opens after Section 2 and the payer works it from the same resource.
Before you start
- A payer-payee relationship between the two Accounts. The body carries its
payerPayeeId. - The payer's scope
payments.payee:writeto create or change a verification, andpayments.payee:readto read one. - For the E-Verify routes, the caller must be the payer.
Endpoints
| Method and path | What it does |
|---|---|
GET /v3/compliance/employment-eligibility-verifications | List verifications across the caller's org branch. Filter by status, payerAccountId, payeeAccountId, externalId and accountIds. |
POST /v3/compliance/employment-eligibility-verifications | Open a Form I-9. |
GET /v3/compliance/employment-eligibility-verifications/{employmentEligibilityVerificationId} | Read one. |
PATCH the same path | Update with JSON Merge Patch. |
POST .../{id}/submit | The Payee signs Section 1. |
POST .../{id}/verify | The payer signs Section 2. |
POST .../{id}/decline | The payer declines. Terminal. |
POST .../{id}/request-correction | The payer sends Section 1 back to the Payee. |
The create, update, submit, verify, decline and request-correction calls accept an Idempotency-Key header.
Status
| Status | Meaning |
|---|---|
PendingPayeeCompletion | The Payee completes and submits Section 1. This is the status on create. |
PendingPayerCompletion | The payer records documents and signs Section 2. |
Accepted | Section 2 is signed and the payer is enrolled in E-Verify. The E-Verify result is pending. |
Verified | Section 2 is signed and no E-Verify result is pending, or E-Verify authorized employment. |
NotAuthorized | E-Verify returned a final nonconfirmation. |
Declined | The payer declined. |
Open a Form I-9
Call POST /v3/compliance/employment-eligibility-verifications. Either side can call it.
| Field | Required | Notes |
|---|---|---|
payerPayeeId | Yes | The PayerPayee record. If it does not resolve, or the caller holds no seat on it, the call returns 404. |
requirementId | Payee: yes. Payer: no. | The EmploymentEligibilityVerification requirement this satisfies. A payee that omits it gets 400. A requirement of another type, or outside this relationship, returns 422. |
externalId | No | Your reconciliation ID, unique per payer account. Payer only. |
metadata | No | Payer only. |
Repeat calls that cite the same requirementId return the verification that is already open. An externalId already used on the payer account returns 409.
curl -X POST "https://api.wingspan.app/v3/compliance/employment-eligibility-verifications" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Idempotency-Key: 6b1f0c1e-3c1a-4f55-9a6f-0b8f3d2c9e11" \
-H "Content-Type: application/json" \
-d '{ "payerPayeeId": "<payerPayeeId>", "externalId": "hire-1042" }'The response is 201 with the verification in PendingPayeeCompletion. Section 1 is pre-filled from the Payee's profile.
Section 1: the Payee
The identity fields in employeeProfile come from the Payee's profile and are read-only. The Payee writes only the citizenship fields, while the status is PendingPayeeCompletion.
citizenship | Also required |
|---|---|
Citizen | Nothing |
NoncitizenNational | Nothing |
LawfulPermanentResident | aNumber |
NoncitizenAuthorizedToWork | Exactly one of aNumber, i94AdmissionNumber or passport |
curl -X PATCH "https://api.wingspan.app/v3/compliance/employment-eligibility-verifications/$ID" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "employeeProfile": { "citizenship": "LawfulPermanentResident", "aNumber": "A123456789" } }'Then call POST .../{id}/submit with { "signature": { "consentAccepted": true } }. The status becomes PendingPayerCompletion. An incomplete Section 1 returns 422.
Section 2: the payer
While the status is PendingPayerCompletion, the payer can update verificationProcess, documents, firstDayOfEmployment, notes, verifierProfile, externalId and metadata.
verificationProcessisInPersonorRemote.Remoterequires the payer to be enrolled in E-Verify.- Each entry in
documentshaslist(A,BorC),type,issuingAuthority, and optionallynumber,expiresAt,isReceiptandfileIds. A valid set is one List A entry (one to three documents), or one List B and one List C document. - A field the caller may not write returns
422. A field outside its status returns409.
Call POST .../{id}/verify with { "signature": { "consentAccepted": true } }. The status becomes Accepted for a payer enrolled in E-Verify, or Verified otherwise. The response carries formId, the completed Form I-9. Its PDF is at GET /v3/compliance/forms/{formId}/pdf. The Form I-9 is the payer's record, so the Payee always sees formId: null. An incomplete Section 2 or an invalid set of documents returns 422.
To send Section 1 back before verifying, call POST .../{id}/request-correction with an optional verifierComment (up to 500 characters). The status returns to PendingPayeeCompletion and the citizenship answers and Section 2 are kept. To reject the Form I-9, call POST .../{id}/decline.
E-Verify
Payer only. The service selects the case from the verification, so you never send a case number or file URL. Read eVerify on the verification to see the case: status, authorization (Authorized or Unauthorized), caseNumber, closureReason, attemptNumber and nextStep. nextStep.code says what happens next.
nextStep.code | Who acts | Call |
|---|---|---|
AwaitGovernmentResult | Nobody | None |
NotifyEmployeeOfMismatch | Payer | GET .../e-verify/notice, then POST .../e-verify/mismatch-decision |
EmployeeContactsAgency | Payee | None |
ComparePhoto | Payer | GET .../e-verify/photo, then POST .../e-verify/photo-match |
UploadDocumentCopy | Payer | POST .../e-verify/document-scan |
ConfirmOrCorrectCaseData | Payer | POST .../e-verify/confirm-data |
CloseWithReason | Payer | GET .../e-verify/closure-reasons, then POST .../e-verify/close |
CloseAndResubmit | Payer | POST .../e-verify/close, then POST .../e-verify/retry |
Every step body carries expectedAttemptId, the eVerify.verificationAttemptId you last read.
| Call | Key body fields |
|---|---|
mismatch-decision | decision (TakeAction or DoNotTakeAction), employeeNotified (must be true) |
photo-match | result (PhotoMatches, PhotoDoesNotMatch or NoPhotoReturned) |
document-scan | frontFileId, backFileId: vault file IDs |
confirm-data | expectedI9Version: the current i9Version |
close | closureReasonCode, optional otherReasonDescription, currentlyEmployedReasonDescription |
retry | replacement (boolean), optional duplicateContinueReasonCode, duplicateContinueReason |
GET .../e-verify/notice returns an employee notice, FurtherActionNotice or ReferralDateConfirmation, in English or Spanish. If E-Verify returned no photo, submit NoPhotoReturned to photo-match. E-Verify responses are private and must not be stored.
Errors
| Status | Cause |
|---|---|
400 | A payee create without requirementId, or a malformed body. |
401, 403 | Missing credentials, or the caller lacks the scope or is not the payer on a payer-only call. |
404 | The verification or payerPayeeId does not exist or is not yours. |
409 | A duplicate externalId, or an action or field that does not fit the current status. |
422 | An incomplete section, an invalid document set, or a field the caller may not write. |
Related pages
Updated 12 minutes ago