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:write to create or change a verification, and payments.payee:read to read one.
  • For the E-Verify routes, the caller must be the payer.

Endpoints

Method and pathWhat it does
GET /v3/compliance/employment-eligibility-verificationsList verifications across the caller's org branch. Filter by status, payerAccountId, payeeAccountId, externalId and accountIds.
POST /v3/compliance/employment-eligibility-verificationsOpen a Form I-9.
GET /v3/compliance/employment-eligibility-verifications/{employmentEligibilityVerificationId}Read one.
PATCH the same pathUpdate with JSON Merge Patch.
POST .../{id}/submitThe Payee signs Section 1.
POST .../{id}/verifyThe payer signs Section 2.
POST .../{id}/declineThe payer declines. Terminal.
POST .../{id}/request-correctionThe payer sends Section 1 back to the Payee.

The create, update, submit, verify, decline and request-correction calls accept an Idempotency-Key header.

Status

StatusMeaning
PendingPayeeCompletionThe Payee completes and submits Section 1. This is the status on create.
PendingPayerCompletionThe payer records documents and signs Section 2.
AcceptedSection 2 is signed and the payer is enrolled in E-Verify. The E-Verify result is pending.
VerifiedSection 2 is signed and no E-Verify result is pending, or E-Verify authorized employment.
NotAuthorizedE-Verify returned a final nonconfirmation.
DeclinedThe payer declined.

Open a Form I-9

Call POST /v3/compliance/employment-eligibility-verifications. Either side can call it.

FieldRequiredNotes
payerPayeeIdYesThe PayerPayee record. If it does not resolve, or the caller holds no seat on it, the call returns 404.
requirementIdPayee: 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.
externalIdNoYour reconciliation ID, unique per payer account. Payer only.
metadataNoPayer 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.

citizenshipAlso required
CitizenNothing
NoncitizenNationalNothing
LawfulPermanentResidentaNumber
NoncitizenAuthorizedToWorkExactly 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.

  • verificationProcess is InPerson or Remote. Remote requires the payer to be enrolled in E-Verify.
  • Each entry in documents has list (A, B or C), type, issuingAuthority, and optionally number, expiresAt, isReceipt and fileIds. 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 returns 409.

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.codeWho actsCall
AwaitGovernmentResultNobodyNone
NotifyEmployeeOfMismatchPayerGET .../e-verify/notice, then POST .../e-verify/mismatch-decision
EmployeeContactsAgencyPayeeNone
ComparePhotoPayerGET .../e-verify/photo, then POST .../e-verify/photo-match
UploadDocumentCopyPayerPOST .../e-verify/document-scan
ConfirmOrCorrectCaseDataPayerPOST .../e-verify/confirm-data
CloseWithReasonPayerGET .../e-verify/closure-reasons, then POST .../e-verify/close
CloseAndResubmitPayerPOST .../e-verify/close, then POST .../e-verify/retry

Every step body carries expectedAttemptId, the eVerify.verificationAttemptId you last read.

CallKey body fields
mismatch-decisiondecision (TakeAction or DoNotTakeAction), employeeNotified (must be true)
photo-matchresult (PhotoMatches, PhotoDoesNotMatch or NoPhotoReturned)
document-scanfrontFileId, backFileId: vault file IDs
confirm-dataexpectedI9Version: the current i9Version
closeclosureReasonCode, optional otherReasonDescription, currentlyEmployedReasonDescription
retryreplacement (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

StatusCause
400A payee create without requirementId, or a malformed body.
401, 403Missing credentials, or the caller lacks the scope or is not the payer on a payer-only call.
404The verification or payerPayeeId does not exist or is not yours.
409A duplicate externalId, or an action or field that does not fit the current status.
422An incomplete section, an invalid document set, or a field the caller may not write.

Related pages


Did this page help you?