A valid request URL is required to generate request examples{
"messageId": "msg_001",
"success": false,
"failureReasons": ["metadata.searchRefId is required for JSON channel requests"],
"processedAt": "2024-01-15T10:30:00.000Z"
}
Submit Inbound Notification
Submit verification responses from employers, schools, and third-party services
A valid request URL is required to generate request examples{
"messageId": "msg_001",
"success": false,
"failureReasons": ["metadata.searchRefId is required for JSON channel requests"],
"processedAt": "2024-01-15T10:30:00.000Z"
}
Submit Inbound Notification
Use this endpoint to submit verification responses from external parties. The system processes responses through various channels (email, forms, fax, and generic JSON) and automatically extracts verification details using AI.Authentication
Bearer Token Required - Include your JWT token in the Authorization header.Quick Examples
# For employment verification
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"messageId": "msg_001",
"threadId": "thread_001",
"channel": "EMAIL",
"messageData": {
"id": "msg_001",
"threadId": "thread_001",
"from": "hr@company.com",
"to": "verifications@theary.ai",
"subject": "Employment Verification - John Doe",
"body": {
"text": "John Doe worked here from 2020-01-15 to 2023-12-31 as Software Engineer."
},
"timestamp": "2026-01-15T10:30:00.000Z"
}
}'
# For education verification
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messageId": "msg_001",
"threadId": "thread_001",
"channel": "EMAIL",
"messageData": {
"id": "msg_001",
"threadId": "thread_001",
"from": "capelookouthighschool@edu.com",
"to": "verifications@theary.ai",
"subject": "Education Verification - John Doe",
"body": {
"text": "John Doe completed with a Highschool Diploma at Cape Lookout High School in Moorehead City NC with graduation date as 2023-05-15"
},
"timestamp": "2024-01-15T10:30:00.000Z"
}
}'
Request Parameters
Optional Fields
msg_<timestamp>_<random>. You can also provide it in messageData.id - the system will use the top-level messageId if present, otherwise messageData.id, otherwise auto-generate.messageId as the threadId. You can also
provide it in messageData.threadId - the system will use the top-level threadId if present, otherwise
messageData.threadId, otherwise use the messageId.Show properties
Show properties
JSON channel requests.JSON channel requests.text and/or html fields. Used by email, fax, form, and ticketing channels. Not used for JSON
channel extraction (use data instead).JSON channel requests. Required when channel is JSON. Pass the vendor
response object as-is. The API enumerates employment or education history records from common vendor shapes (see Supported
JSON payload shapes). An empty object fails with No JSON payload found.id
(string) - Unique attachment identifier - name (string) - Attachment filename - contentType (string) - MIME type (e.g.,
“application/pdf”) - size (number) - File size in bytes - content (string, optional) - Base64-encoded file content
(required for PDF processing)Optional Fields
STANDARD) Available Values:
STANDARD, EMAIL, EFORM, MICROSOFT_GRAPH, FAX_WEBHOOK, VOICE_TRANSCRIPT, TICKETING, JITBIT, JSON Channel Types:
STANDARD- Default whenchannelis omittedEMAIL- Email responsesEFORM- Electronic formsMICROSOFT_GRAPH- Office 365FAX_WEBHOOK- Fax responsesVOICE_TRANSCRIPT- Phone/voice transcription responsesTICKETING/JITBIT- Ticketing systemsJSON- Generic structured JSON payloads from upstream systems
Show properties
Show properties
JSON channel. Identifies the verification search to update. Matched against the search’s
externalSearchId or internal reference number. For JSON requests, the API uses this value exclusively and does not scan
the payload for reference numbers in the message body.JSON channel traceability, e.g. equifax, thomas-and-company).verifiedInfo is present, the API may skip
re-extraction. See the OpenAPI schema for verificationResult values.Attachments
Attachments are supported for PDF files. PDFs are automatically processed to extract text content, which is then used during message classification and verification data extraction. Supported Format:- PDF files (
application/pdf) - Fully supported with text extraction - Other file types may be included but will not be processed
- PDFs are processed using document extraction tools
- Extracted text is included in the message analysis
- Processing errors are logged but do not block message processing
Response
Fields:
type- Classification type (see Classification Types below)confidence- Score from 0 to 1 indicating classification confidence
-reasoning- Explanation of the classification decision
extractedData. This temporary
backward-compatible shape applies to every channel. Each object includes
backward-compatible flat provided* / verified* values (including applicant names and providedReasonLeft) and the canonical fields comparison map. See the
EmploymentVerification object for field definitions.employmentVerification, omitted from extractedData. This is the single
highest-confidence provider education-history object for every channel. Each object includes backward-compatible flat
provided* / verified* values and the canonical fields comparison map. See the EducationVerification
object for field definitions.employmentVerification and educationVerification temporarily return one object for backward compatibility. A future
version will return sorted arrays after consumers migrate to the array contract.JSON.
Does not include duplicate employmentVerification / educationVerification objects when those are returned at the response
root.Show common fields
Show common fields
verified, no_record, third_party).employmentVerification.startDate, endDate, etc.) for backward compatibility.positionTitle) for backward compatibility.JSON channel. true when a critical mismatch is detected on the best-match history row under tenant comparison settings. Field-level comparison details are on root-level employmentVerification.fields / educationVerification.fields, not in extractedData.applicantMismatches and applicantDiscrepancies are not returned in webhook or inbound notification payloads (removed). Use fields.*.status, expected, extracted, and comparison on the verification object instead.JSON channel. Present when a critical mismatch on the best-match record triggers manual review routing
(applicantMismatch: true). May appear in both the synchronous response and downstream inbound-notification or
action-required webhooks.JSON channel. true when extractedData was produced from a structured third-party JSON payload rather than
email/fax/voice extraction. Present in the synchronous response and downstream payloads.searchId, outcome, and channelClassification Types
The classificationtype field is one of the INTENT enum values assigned by the inbound processing pipeline:
VERIFICATION- Verification confirmed with detailsINFO_REQUEST- Employer/school needs more information, or a general information requestINFO_PROVIDED- Applicant provided information that may need follow-upNEXT_CONTACT- Indicates a different contact at the entity should be reached nextWRONG_ENTITY- Message indicates the wrong organization/entity was contactedRECORD_NOT_FOUND- No employment/education record foundAUTHORIZATION_FORM_REQUEST- Authorization form is needed before the entity will respondNO_ACTION_REQUIRED- Message requires no further actionUNKNOWN- Could not determine intentOTHER- Other classification that doesn’t fit the above
THIRD_PARTY is a deprecated legacy value — the classifier remaps it to WRONG_ENTITY internally, so it will never appear in a response’s classification.type.Examples
Employment Verification
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"messageId": "emp_001",
"threadId": "thread_abc",
"channel": "EMAIL",
"messageData": {
"id": "emp_001",
"threadId": "thread_abc",
"from": "hr@techcorp.com",
"to": "verifications@theary.ai",
"subject": "Re: Employment Verification - John Doe",
"body": {
"text": "Reference: 11111111\n\nJohn Doe was employed as Software Engineer from 2020-01-15 to 2023-12-31. Employee ID: EMP001."
},
"timestamp": "2026-01-15T10:30:00.000Z"
}
}'
const response = await fetch('https://sandbox.theary.ai/background-check/v1/inbound/notifications', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_JWT_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
messageId: 'emp_001',
threadId: 'thread_abc',
channel: 'EMAIL',
messageData: {
id: 'emp_001',
threadId: 'thread_abc',
from: 'hr@techcorp.com',
to: 'verifications@theary.ai',
subject: 'Re: Employment Verification - John Doe',
body: {
text: 'John Doe was employed as Software Engineer from 2020-01-15 to 2023-12-31. Employee ID: EMP001.',
},
timestamp: '2026-01-15T10:30:00.000Z',
},
}),
})
const result = await response.json()
import requests
response = requests.post(
'https://sandbox.theary.ai/background-check/v1/inbound/notifications',
headers={
'Authorization': 'Bearer YOUR_JWT_TOKEN',
'Content-Type': 'application/json'
},
json={
"messageId": "emp_001",
"threadId": "thread_abc",
"channel": "EMAIL",
"messageData": {
"id": "emp_001",
"threadId": "thread_abc",
"from": "hr@techcorp.com",
"to": "verifications@theary.ai",
"subject": "Re: Employment Verification - John Doe",
"body": {
"text": "Reference: 11111111\n\nJohn Doe was employed as Software Engineer from 2020-01-15 to 2023-12-31. Employee ID: EMP001."
},
"timestamp": "2026-01-15T10:30:00.000Z"
}
}
)
result = response.json()
employmentVerification object with per-field comparison in fields
(not nested in extractedData). Example from an EMAIL channel response for Pablo’s Pizza / John Smith:
{
"messageId": "emp_001",
"success": true,
"classification": {
"type": "VERIFICATION",
"confidence": 0.85,
"reasoning": "Entity verification"
},
"extractedData": {
"outcome": "verified",
"employerName": "Pablo's Pizza",
"jobTitle": "Senior Software Developer",
"startDate": "2022-01-01",
"endDate": "2023-01-01",
"extractedApplicantName": "John Smith",
"verifiedDates": {
"startDate": "2022-01-01",
"endDate": "2023-01-01"
},
"verifiedContent": {
"positionTitle": "Senior Software Developer"
},
"refId": "EXT-DEV-1781045734449-1",
"searchType": "EMPLOYMENT"
},
"employmentVerification": {
"fields": {
"companyName": {
"type": "string",
"status": "match",
"expected": "Pablo's Pizza",
"extracted": "Pablo's Pizza",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"position": {
"type": "string",
"status": "match",
"expected": "Senior Software Developer",
"extracted": "Senior Software Developer",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"startDate": {
"type": "date",
"status": "match",
"expected": "2022-01-01",
"extracted": "2022-01-01",
"comparison": { "engine": "deterministic", "distance": 0 }
},
"endDate": {
"type": "date",
"status": "match",
"expected": "2023-01-01",
"extracted": "2023-01-01",
"comparison": { "engine": "deterministic", "distance": 0 }
},
"applicantName": {
"type": "string",
"status": "match",
"expected": "John Smith",
"extracted": "John Smith",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"salary": {
"type": "number",
"status": "match",
"expected": "100000",
"extracted": "100000",
"comparison": { "engine": "deterministic", "distance": 0 }
},
"location": {
"type": "string",
"status": "not_checked",
"expected": null,
"extracted": "Grand Junction, CO",
"comparison": { "engine": "none" }
},
"contactNumber": {
"type": "string",
"status": "not_checked",
"expected": null,
"extracted": "+1-555-200-1000",
"comparison": { "engine": "none" }
}
},
"doNotContact": false,
"comments": "Email confirms employment at Pablo's Pizza for John Smith in the stated role and dates."
},
"extractedData": {
"employmentVerified": true,
"position": "Software Engineer",
"startDate": "2020-01-15",
"endDate": "2023-12-31"
},
"failureReasons": [],
"metadata": {
"contactAnalysis": "ENTITY",
"searchId": "ae98cdb9-e7b9-4a44-b4e2-d4aa565a2f66",
"referenceNumbers": ["EXT-DEV-1781045734449-1"],
"channel": "EMAIL",
"outcome": "verified"
},
"processedAt": "2026-06-09T23:42:32.105Z"
}
Education Form Submission
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"messageId": "edu_001",
"threadId": "thread_xyz",
"channel": "EFORM",
"messageData": {
"id": "edu_001",
"threadId": "thread_xyz",
"from": "registrar@university.edu",
"to": "verifications@theary.ai",
"subject": "Education Verification - Jane Smith",
"body": {
"text": "Reference: 22222222\n\nStudent: Jane Smith\nDegree: BS in Computer Science\nGraduation: 2019-05-15\nGPA: 3.8\nTranscript attached separately via email."
},
"timestamp": "2026-01-15T11:45:00.000Z"
},
"metadata": {
"formType": "education_verification"
}
}'
{
"messageId": "edu_001",
"success": true,
"classification": {
"type": "VERIFICATION",
"confidence": 0.95,
"reasoning": "Electronic form with structured education verification data"
},
"metadata": {
"searchId": "789e0123-e89b-12d3-a456-426614174002",
"outcome": "verified"
},
"processedAt": "2026-01-15T11:45:03.000Z"
}
Generic JSON Submission
Usechannel: "JSON" when an upstream system sends structured third-party verification data instead of an email, fax, or ticket transcript. Place the provider payload in messageData.data.
JSON channel requirements:
| Field | Required | Notes |
|---|---|---|
channel | Yes | Must be "JSON" |
messageData.timestamp | Yes | ISO 8601 timestamp |
messageData.data | Yes | Non-empty provider payload object |
metadata.searchRefId | Yes | Search identifier (externalSearchId or internal reference) |
messageId, threadId | No | Auto-generated when omitted |
messageData.from, messageData.to | No | Defaults to metadata.provider and "JSON" for traceability |
Supported JSON payload shapes
The API detects employment or education history records from common vendor response layouts. You do not need to normalize the payload to a single schema — pass the vendor object inmessageData.data.
Employment searches — records are enumerated from:
| Path | Typical vendor |
|---|---|
employmentData[] | Equifax Talent Report Employment Select All (TrEsaResponse.data) |
data.employmentData[] | Wrapped Equifax employment responses |
Data[] | Thomas & Company Employment Response V2 |
Other arrays at depth ≤ 2 containing employer/employee, Employer/Employee, Positions, or WorkHistories keys | Fallback detection |
| Path | Typical vendor |
|---|---|
educationVerification.degreeVerification[] | Equifax Talent Report Education |
educationVerification.enrollmentVerification[] | Equifax Dates of Attendance |
...degreeVerification[].degreeDetails.degreeDetail[] | Nested degree records |
...degreeVerification[].enrollmentDetails.enrollmentDetail[] | Nested enrollment under degree verification |
...enrollmentVerification[].enrollmentDetails.enrollmentDetail[] | Nested enrollment under enrollment verification |
Other arrays at depth ≤ 2 containing degreeTitle, officialNameOfSchool, nameOnSchoolRecord, etc. | Fallback detection |
Employment JSON example
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"channel": "JSON",
"messageData": {
"data": {
"employmentData": [
{
"employer": { "name": "Pablos Pizza" },
"employee": {
"firstName": "John",
"lastName": "Smith",
"positionTitle": "Senior Software Engineer",
"mostRecentHireDate": "2022-01-01",
"terminationDate": "2023-01-01",
"workLocation": {
"address": { "city": "Grand Junction", "state": "CO" }
}
}
},
{
"employer": { "name": "SNH AI" },
"employee": {
"firstName": "John",
"lastName": "Smith",
"positionTitle": "Software Engineer",
"mostRecentHireDate": "2020-01-01",
"terminationDate": "2023-01-01",
"workLocation": {
"address": { "city": "Austin", "state": "TX" }
}
}
},
{
"employer": { "name": "Other Corp" },
"employee": {
"firstName": "John",
"lastName": "Smith",
"positionTitle": "Software Intern",
"mostRecentHireDate": "2019-01-01",
"terminationDate": "2020-01-01",
"workLocation": {
"address": { "city": "Denver", "state": "CO" }
}
}
}
]
},
"timestamp": "2026-01-15T12:00:00.000Z"
},
"metadata": {
"provider": "test-provider",
"searchRefId": "EXT-DEV-1781042158637-1"
}
}'
extractedData contains the best-match
scalars and operational flags, while root-level employmentVerification contains the single highest-confidence provider history
object.
Education JSON example
curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel": "JSON",
"messageData": {
"data": {
"educationVerification": {
"degreeVerification": [
{
"degreeDetails": {
"degreeDetail": [
{
"officialNameOfSchool": "Cape Lookout High School",
"degreeTitle": "High School Diploma",
"awardDate": "2023-05-15",
"nameOnSchoolRecord": {
"firstName": "John",
"lastName": "Doe"
}
}
]
}
}
]
}
},
"timestamp": "2024-01-15T12:00:00.000Z"
},
"metadata": {
"provider": "equifax",
"searchRefId": "EXT-EDU-12345678"
}
}'
channel: "JSON" submissions return the same synchronous processing envelope as other channels: messageId, success,
classification, extractedData, the applicable root-level employmentVerification / educationVerification object, metadata,
and processedAt. The verification object is the highest-confidence provider history row. The employment example selects the row
with confidence 0.94 for search EXT-DEV-1781042158637-1. The raw
provider payload is not echoed in the synchronous response or webhook payloads. For the full verification.completed JSON closeout
shape, see Webhook events — Employment Verification (JSON
channel).
Comparison fields: selected JSON record
JSON channel responses expose comparison output at two levels:| Location | Purpose |
|---|---|
Root-level employmentVerification / educationVerification (sync API response, verification.notification, activity metadata, verification.action_required, verification.completed) | Canonical selected verification — the highest-confidence normalized object with per-field fields.*.comparison metadata. Omitted from extractedData / metadata.extractedData. All externally returned field-level comparison (matches, mismatches, and match_with_discrepancy) lives here. |
extractedData.applicantMismatch, _requiresManualReview | Operational flags (best-match row only) — boolean escalation signals when a critical mismatch triggers manual review. Does not include field-level detail. |
employmentVerification.fields / educationVerification.fields | Selected-row detail — comparison detail for the highest-confidence record. Replaces the removed applicantMismatches / applicantDiscrepancies objects. |
Field comparison metadata (fields.*.comparison)
Normalized verification objects (for example employmentVerification.fields on downstream webhooks) attach per-field comparison metadata. similarity is not a universal score — deterministic exact string matches use similarity: 1, and probabilistic / organization-name comparisons use their evaluated score. Date and number fields never include similarity; deterministic matches and mismatches on those types use distance instead.
| Field type | Example fields | Match (status: "match") | Mismatch (status: "mismatch") | Discrepancy (status: "match_with_discrepancy") |
|---|---|---|---|---|
date | startDate, endDate, graduationDate | comparison.engine: deterministic. comparison.distance: calendar days between expected and extracted (0 for exact dates). No similarity. | comparison.engine: deterministic. comparison.distance: calendar days between expected and extracted. No similarity. | Not used for dates. |
number | salary, gpa | comparison.engine: deterministic. comparison.distance: absolute difference (0 for exact numbers). No similarity. | comparison.engine: deterministic. comparison.distance: absolute difference (for salary, normalized annual USD when both sides parse). No similarity. | Not used for numbers. |
string (LLM semantic / proximity) | applicantName, position, degree, major, location | Exact normalized match: comparison.engine: deterministic. comparison.similarity: 1. | comparison.engine: probabilistic when LLM evaluates the pair. comparison.similarity: LLM score (0–1); falls back to 0 when no score is available. | comparison.engine: probabilistic. comparison.similarity: LLM score (0–1). |
string (organization name) | companyName, schoolName | Exact normalized match: comparison.engine: deterministic. comparison.similarity: 1. | May use probabilistic LLM fallback or deterministic organization scoring with similarity; deterministic mismatches without an evaluated score use 0. | Typo-tolerant match: comparison.engine: deterministic or probabilistic. May include comparison.similarity from string-similarity or LLM scoring. |
"location": {
"type": "string",
"status": "mismatch",
"expected": "Grand Junction, CO",
"extracted": "Austin, TX",
"reason": "The locations are in different states and are not geographically close.",
"comparison": { "engine": "probabilistic", "similarity": 0.1 }
}
"location": {
"type": "string",
"status": "match_with_discrepancy",
"expected": "Phoenix, AZ",
"extracted": "Scottsdale, AZ",
"reason": "The locations are highly similar because Scottsdale is in the Phoenix metro area.",
"comparison": { "engine": "probabilistic", "similarity": 0.95 }
}
distance: 0, no similarity):
"startDate": {
"type": "date",
"status": "match",
"expected": "2022-01-01",
"extracted": "2022-01-01",
"reason": "exact match",
"comparison": { "engine": "deterministic", "distance": 0 }
}
similarity present):
"position": {
"type": "string",
"status": "match_with_discrepancy",
"expected": "Senior Software Developer",
"extracted": "Senior Software Engineer",
"reason": "Software Developer and Software Engineer describe the same engineering function.",
"comparison": { "engine": "probabilistic", "similarity": 0.98 }
}
Downstream webhook delivery
In addition to the synchronous response body, successful JSON submissions emit extraction and the selected normalized verification object asynchronously. Primary delivery paths:verification.notificationwithnotificationType: "INBOUND_RECEIVED_DETAIL"— best-match scalars (and optionalapplicantMismatch/_requiresManualReviewflags) atdata.metadata.extractedData; the highest-confidence record with per-field comparison at root-leveldata.employmentVerificationordata.educationVerification(not nested inextractedData).applicantMismatchesandapplicantDiscrepanciesare not included.verification.action_requiredwithreasonCode: "HUMAN_ESCALATION"or"OTHER"— when manual review is required for non-verified or JSON-channel holds;extractedDataunderdata.metadata.extractedDatawithout duplicate verification data. JSON-channel only (channel: "JSON",status: "QA Review"): root-leveldata.message(human-readable review summary for client UI) plus the highest-confidencedata.employmentVerificationordata.educationVerificationobject. EMAIL/FAX/VOICE action-required webhooks omitmessage. Verified EMAIL/FAX/VOICE with field mismatches typically close viaverification.completedandverificationResult.discrepancy: trueinstead.- Activity metadata — root-level
employmentVerification/educationVerificationplus strippedextractedData(same split as webhooks), alongsidemessageId,threadId,contactType, andmessageContext verification.completed— root-leveldata.employmentVerification(ordata.educationVerification) as the single highest-confidence object for JSON.data.verificationResult.extractedDatakeeps scalar extraction and comparison metadata without duplicate verification data.verificationResult.discrepancyis the operational closeout flag and may befalsewhen the selected row only has non-criticalfields.*.status: "match_with_discrepancy"entries.
INBOUND_RECEIVED_DETAIL webhook envelope for search 86bc0a43-6a10-4f85-a093-e4244ed78107 / EXT-DEV-1781042158637-1 (Pablo’s Pizza / John Smith). metadata.extractedData is identical wherever that object appears; the selected verification object sits at the payload root.
{
"event": "verification.notification",
"occurredAt": "2026-06-09T22:00:00.000Z",
"data": {
"searchId": "86bc0a43-6a10-4f85-a093-e4244ed78107",
"externalSearchId": "EXT-DEV-1781042158637-1",
"searchType": "EMPLOYMENT",
"notificationType": "INBOUND_RECEIVED_DETAIL",
"from": "test-provider",
"classification": {
"type": "VERIFICATION",
"confidence": 0.94
},
"employmentVerification": {
"confidence": 0.94,
"fields": {
"companyName": {
"type": "string",
"status": "match",
"expected": "Pablo's Pizza",
"extracted": "Pablos Pizza",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"position": {
"type": "string",
"status": "match_with_discrepancy",
"expected": "Senior Software Developer",
"extracted": "Senior Software Engineer",
"comparison": { "engine": "probabilistic", "similarity": 0.98 }
},
"startDate": {
"type": "date",
"status": "mismatch",
"expected": "2022-01-01",
"extracted": "2021-01-01",
"comparison": { "engine": "deterministic", "distance": 365 }
},
"endDate": {
"type": "date",
"status": "match",
"expected": "2023-01-01",
"extracted": "2023-01-01",
"comparison": { "engine": "deterministic", "distance": 0 }
},
"location": {
"type": "string",
"status": "match",
"expected": "Grand Junction, CO",
"extracted": "Grand Junction, CO",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"applicantName": {
"type": "string",
"status": "match",
"expected": "John Smith",
"extracted": "John Smith",
"comparison": { "engine": "deterministic", "similarity": 1 }
}
}
},
"metadata": {
"source": "inbound",
"channel": "JSON",
"timestamp": "2026-06-09T22:00:00.000Z",
"to": "JSON",
"upstreamProvider": "test-provider",
"extractedData": {
"outcome": "verified",
"refId": "EXT-DEV-1781042158637-1",
"searchType": "EMPLOYMENT",
"employerName": "Pablos Pizza",
"jobTitle": "Senior Software Engineer",
"startDate": "2021-01-01",
"endDate": "2023-01-01",
"employerLocation": "Grand Junction, CO",
"extractedApplicantName": "John Smith",
"_processedFromJson": true,
"_thirdPartyProvider": "test-provider",
"applicantMismatch": true,
"_requiresManualReview": true
}
}
}
}
{
"event": "verification.notification",
"occurredAt": "2026-06-09T22:00:00.000Z",
"data": {
"searchId": "86bc0a43-6a10-4f85-a093-e4244ed78107",
"externalSearchId": "EXT-DEV-1781042158637-1",
"searchType": "EMPLOYMENT",
"notificationType": "INBOUND_RECEIVED_DETAIL",
"from": "test-provider",
"classification": { "type": "VERIFICATION", "confidence": 0.94 },
"employmentVerification": {
"confidence": 0.94,
"fields": {
"companyName": {
"type": "string",
"status": "match",
"expected": "Pablo's Pizza",
"extracted": "Pablos Pizza",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"position": {
"type": "string",
"status": "match_with_discrepancy",
"expected": "Senior Software Developer",
"extracted": "Senior Software Engineer",
"comparison": { "engine": "probabilistic", "similarity": 0.98 }
},
"startDate": {
"type": "date",
"status": "match",
"expected": "2022-01-01",
"extracted": "2022-01-01",
"comparison": { "engine": "deterministic", "distance": 0 }
},
"endDate": {
"type": "date",
"status": "match",
"expected": "2023-01-01",
"extracted": "2023-01-01",
"comparison": { "engine": "deterministic", "distance": 0 }
},
"location": {
"type": "string",
"status": "match",
"expected": "Grand Junction, CO",
"extracted": "Grand Junction, CO",
"comparison": { "engine": "deterministic", "similarity": 1 }
},
"applicantName": {
"type": "string",
"status": "match",
"expected": "John Smith",
"extracted": "John Smith",
"comparison": { "engine": "deterministic", "similarity": 1 }
}
}
},
"metadata": {
"source": "inbound",
"channel": "JSON",
"timestamp": "2026-06-09T22:00:00.000Z",
"to": "JSON",
"upstreamProvider": "test-provider",
"extractedData": {
"outcome": "verified",
"refId": "EXT-DEV-1781042158637-1",
"searchType": "EMPLOYMENT",
"employerName": "Pablos Pizza",
"jobTitle": "Senior Software Engineer",
"startDate": "2022-01-01",
"endDate": "2023-01-01",
"employerLocation": "Grand Junction, CO",
"extractedApplicantName": "John Smith",
"_processedFromJson": true,
"_thirdPartyProvider": "test-provider"
}
}
}
}
employmentVerification / educationVerification is the highest-confidence object. Field-level comparison for that selected row appears on data.employmentVerification.fields / data.educationVerification.fields. applicantMismatches and applicantDiscrepancies are not included in webhook or inbound notification payloads — use fields.* instead. When a critical mismatch triggers escalation, metadata.extractedData may include applicantMismatch: true and _requiresManualReview: true without field-level objects. When the search closes without escalation, verification.completed may set verificationResult.discrepancy: false even though employmentVerification.fields.position.status is match_with_discrepancy — see Webhook events.
Auto-Generated IDs
When messageId or threadId are not provided, the system automatically generates them:curl -sS -X POST "https://sandbox.theary.ai/background-check/v1/inbound/notifications" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"channel": "EMAIL",
"messageData": {
"from": "hr@company.com",
"to": "verifications@theary.ai",
"subject": "Employment Verification - John Doe",
"body": {
"text": "Reference: 11111111\n\nJohn Doe worked here from 2020-01-15 to 2023-12-31 as Software Engineer."
},
"timestamp": "2026-01-15T10:30:00.000Z"
}
}'
messageId and threadId if not provided. The generated IDs will be returned in the response.Error Responses
200 (the controller forces @HttpCode(HttpStatus.OK)), even when processing fails. Failures are represented in the response body, not the HTTP status — check the success and failureReasons fields on every response.{
"messageId": "msg_001",
"success": false,
"failureReasons": ["metadata.searchRefId is required for JSON channel requests"],
"processedAt": "2024-01-15T10:30:00.000Z"
}
{
"messageId": "msg_001",
"success": false,
"failureReasons": ["No JSON payload found"],
"processedAt": "2024-01-15T10:30:00.000Z"
}
{
"messageId": "msg_001",
"success": false,
"failureReasons": ["Search EXT-DEV-1781042158637-1 not found in verification system"],
"processedAt": "2024-01-15T10:30:00.000Z"
}
{
"messageId": "msg_001",
"success": false,
"failureReasons": ["Search not found"],
"processedAt": "2024-01-15T10:30:00.000Z"
}
{
"statusCode": 400,
"message": "messageData.from, messageData.to required for EMAIL channel",
"error": "Bad Request"
}
{
"statusCode": 401,
"message": "Unauthorized"
}
{
"statusCode": 400,
"message": ["messageData.timestamp should not be empty", "channel must be a valid enum value"],
"error": "Bad Request"
}
400 Bad Request shape as every other endpoint — there is no 422 status configured for this API. messageId is optional and can never trigger a “should not be empty” failure; messageData.timestamp is the field actually marked as required.
{
"statusCode": 400,
"message": "messageData.from, messageData.to required for EMAIL channel",
"error": "Bad Request"
}
JSON channels also require messageData.from and messageData.to; the controller checks this explicitly and returns a single-string 400 message (not the array shape) naming the missing fields.
How It Works
The inbound notification processing follows these steps:- Submit - You send verification response via API
- ID Generation - If
messageIdorthreadIdare not provided, they are auto-generated - Validate - System validates required fields and checks for malicious content. For
JSONchannel, validates thatmetadata.searchRefIdand a non-emptymessageData.datapayload are present. For other channels, validatesmessageData.fromandmessageData.to. - Match - Links message to active verification search:
JSONchannel: Matches exclusively onmetadata.searchRefId(external search ID or internal reference). Does not scan the payload body for reference numbers.- Other channels: Extracts 7–8 digit reference numbers from subject, body, attachments, or uses
metadata.searchRefId/threadIdwhen provided.
- Process Attachments - PDF attachments are processed to extract text content (if present; skipped for
JSONchannel) - Analyze Contact - Determines sender type (APPLICANT, ENTITY, or UNKNOWN). JSON channel requests are treated as ENTITY responses.
- Classify & Extract - AI-powered classification extracts verification details:
JSONchannel: Extracts directly from structured provider payload; selects the best-matching employment or education record when multiple are present.- Other channels: Analyzes the message content to extract structured verification data.
- Extracts structured outcome (VERIFIED, NO_RECORD, THIRD_PARTY, INFO_REQUESTED, etc.)
- Parses verification data (dates, positions, confirmation status)
- Cross-check Provided Data - Compares extracted applicant, employment, or education details against the order data on file. Tenant comparison settings (see tenant
config.comparisonConfig) tune behavior: calendar-day windows per date field (startDate,endDate,graduationDate), semantic equivalence thresholds for applicant name, job title, degree, major, and location proximity, and salary tolerances. For employment salary, when both the order value (providedSalaryin search metadata) and the extracted salary parse, the API normalizes common compensation text (for example annual, hourly, monthly, weekly, andk/msuffixes) to an estimated annual USD amount and treats a mismatch only if the gap exceeds both the configured absolute-dollar threshold and the relative (percent) threshold; otherwise it falls back to the same normalized string comparison used for other plain-text fields. Mismatch reasons for dates and salaries include the compared raw values (and annualized figures for salary) to aid review. - Persist Results - Saves processing results, classification, and extracted data
- Update Search - Updates search status if terminal outcome (VERIFIED, NO_RECORD, THIRD_PARTY, WRONG_ORG)
- Schedule Follow-up - If non-terminal outcome (INFO_REQUESTED, INSUFFICIENT), schedules next outbound attempt
- Notify - Sends webhook if verification reaches terminal state
Common Use Cases
| Use Case | Channel | Best For |
|---|---|---|
| Email from HR | EMAIL | Employment verification responses |
| University form | EFORM | Education verification with transcripts |
| Fax response | FAX_WEBHOOK | Legacy systems, healthcare, legal |
| Office 365 | MICROSOFT_GRAPH | Enterprise email automation |
| Help desk | TICKETING | Support ticket workflows |
| Generic JSON | JSON | Structured third-party provider payloads |
Best Practices
Message IDs:- Use unique identifiers (UUIDs recommended) if available
- Never reuse message IDs
- Optional: Omit the field entirely if you don’t have one - the system will auto-generate it
- If provided, ensure it’s unique across all messages
- Keep consistent for related messages in the same conversation thread
- Helps track conversation history across multiple exchanges
- Optional: Omit the field entirely if you don’t have one - the system will use the messageId as the threadId
- Use the same threadId for all messages in the same conversation
- Always use ISO 8601 format
- Example:
2026-01-15T10:30:00.000Z
- Include all verification details in the
body.textfield - Include reference number: The message must contain a 7-8 digit reference number to match the message to the correct verification search. The system looks for patterns like:
- “Reference: 12345678”
- “Ref. 12345678”
- “Ticket # 12345678”
- “Case # 12345678”
- Or standalone 7-8 digit numbers
- Mention attachments sent via separate channels if needed
JSON channel): - Set
channelto"JSON"and place the vendor response inmessageData.data - Always include
metadata.searchRefId— this is the only search-matching mechanism for JSON requests. Use the search’s external ID (e.g.EXT-DEV-1781042158637-1) or internal reference number - Set
metadata.providerto identify the upstream system for audit and traceability - Pass the vendor payload as-is; normalization to a single schema is not required
- When the provider returns multiple employment or education records, include all of them — the API selects the record that best matches the applicant and submitted search data
- For every channel, HTTP 200 includes a response body; check
successand processfailureReasonswhen present - Successful responses include best-match scalars in
extractedDataand the highest-confidence normalized provider record in the applicable root-level verification object - Implement retry with exponential backoff for server errors
Troubleshooting
“metadata.searchRefId is required for JSON channel requests”- Include
metadata.searchRefIdon everychannel: "JSON"request - The value must be a non-empty string
- Ensure
messageData.datais a non-empty object containing at least one enumerable employment or education record - Do not send only
{ "text": "..." }or{ "html": "..." }keys without structured verification data
- Verify
metadata.searchRefIdmatches the search’sexternalSearchIdor internal reference - Ensure the search is still active (not completed or cancelled)
- Check that the search has the INBOUND objective enabled
- Verify the message contains a valid 7-8 digit reference number in one of the supported formats:
- “Reference: 12345678”
- “Ref. 12345678”
- “Ticket # 12345678”
- “Case # 12345678”
- Or a standalone 7-8 digit number
- Ensure the reference number matches an active verification search
- Ensure search is still active (not completed or cancelled)
- Check that the search has the INBOUND objective enabled
- Check sender email matches expected contact (if applicable)
- Non-JSON channels require both
messageData.fromandmessageData.to - These fields are optional only when
channelis"JSON"
- All required fields must be present for the channel type
- Check timestamp is valid ISO 8601
- Review error messages in response
- Verify data types match requirements
- Ensure all required fields are present
Next Steps
Body
Message data
Show child attributes
Show child attributes
Message ID to process (auto-generated if not provided)
Thread ID (auto-generated if not provided)
Channel type
MICROSOFT_GRAPH, FAX_WEBHOOK, STANDARD, EMAIL, TICKETING, JITBIT, EFORM, JSON Additional metadata. Can include pre-extracted data from upstream systems: - provider: Source system identifier (e.g., 'SNH AI ', 'JITBIT') - searchRefId: Reference ID for the search - activityId: Activity ID from upstream system - link: URL to original ticket/message - intention: Pre-classified intent (VERIFICATION, INFO_REQUEST, THIRD_PARTY, etc.) - confidence: Confidence level from upstream (0-1). Defaults to 0.95 if not provided. - structuredData: Pre-extracted verification data with verifiedInfo and verificationResult
When structuredData is provided with verifiedInfo, the system will use this
pre-extracted data instead of re-processing, improving accuracy and efficiency.
Supported verificationResult values: VERIFIED, VERIFIED_DISCREPANCY, VERIFIED_NO_DISCREPANCY,
UNABLE_TO_VERIFY, UNVERIFIED, NO_RECORD, RECORD_NOT_FOUND, THIRD_PARTY, WRONG_ENTITY
{
"provider": "UPSTREAM_SYSTEM",
"searchRefId": "12345678",
"intention": "VERIFICATION",
"confidence": 0.95,
"structuredData": {
"verifiedInfo": {
"subjectName": "John Smith",
"employerName": "Tech Corp",
"startDate": "2020-01-15",
"endDate": "2023-06-30",
"position": "Software Engineer"
},
"verificationResult": "VERIFIED"
}
}
Response
The response is of type object.

