Webhook Events
SNH AI delivers webhook events to notify your application about verification status changes in real-time. This document provides comprehensive documentation of all event types, payload structures, field definitions, and examples from the actual verification system.Payload Structure
All webhook events follow a consistent structure with three top-level fields:Top-Level Fields
Payload Enrichment
The system automatically enriches webhook payloads with additional context: For all events withsearchId:
externalSearchId: Your external identifier provided at order creation
verification.completed, verification.action_required):
channel: Single terminal channel used (EMAIL,VOICE,FAX, orJSON)- Root-level highest-confidence
employmentVerificationoreducationVerificationobjects containing both per-field comparison infieldsand backward-compatible flatprovided*/verified*properties — not nested inverificationResultormetadata.extractedData message(JSON-channelverification.action_requiredwithstatus: "QA Review"only): Human-readable review summary for client UI — plain-text breakdown of each third-party history record when_processedFromJsonis true. Not present on EMAIL, FAX, or VOICE action-required webhooks (includingstatus: "QA Review"). EMAIL/FAX/VOICE verified inbound with field conflicts typically close viaverification.completedandverificationResult.discrepancy: trueinstead.discrepancy(onverification.completedonly): Operational closeout flag onverificationResult; may befalsewhen the best-match row only has non-criticalmatch_with_discrepancyfield statuses under tenant comparison settings
verification.notification with INBOUND_RECEIVED_DETAIL):
- Root-level
employmentVerificationoreducationVerificationwhen normalized rows are available, with bothfieldscomparison metadata and backward-compatible flat properties - Scalar extraction under
metadata.extractedDatawithout duplicate verification objects or removedapplicantMismatches/applicantDiscrepanciesobjects - Optional
applicantMismatch/_requiresManualReviewboolean flags when escalation is triggered
Verification fields temporarily return one object for backward compatibility. A future version will return sorted
employmentVerification and educationVerification arrays after consumers migrate to the array contract.accreditation: Institutional accreditation outcome (education searches only)
WebhookEventType Enum
Pause/resume outbound updates are delivered as
verification.notification with notificationType values such as OUTBOUND_PAUSED, OUTBOUNDS_PAUSED, OUTBOUND_RESUMED, and OUTBOUNDS_RESUMED.
HTTP Headers
Every webhook request includes these headers:Signature Verification
Webhooks are signed with HMAC-SHA256 (X-Webhook-Signature: sha256=<hex>). Always verify using the raw request body bytes. Full verification examples (Node.js, Python, OpenSSL) live in Webhook Integration.
Common Enums
SearchType
VerificationChannel
StandardOutcomeCode
Event: verification.completed
Sent when a verification search completes with results.Data Fields
VerificationResult Object
VerifierProfile Object
Verification questions on completed events
EMPLOYMENT and EDUCATION searches pin a questionnaire (standard questions always; custom questions when you sendverificationQuestions at order creation). On verification.completed, answers appear in:
Each question includes request fields plus
fieldKey, origin ("standard" | "custom"), selectedAnswer, comment, and optional conditionMet.
Answer shapes by type:
Incomplete or invalid required answers on PDF/fax/voice inbound do not produce
verification.completed — they escalate via verification.action_required instead (see Questionnaire-related metadata).
Full authoring rules, channel selection, and request examples: Verification question.
Example: completed payload with custom question answers
In a live payload,
questionnaire.questions matches verificationQuestions exactly (abbreviated as [] above).Understanding Provided vs Verified Fields
The verification system tracks two sets of data for each verification: Provided Fields (provided*):
- Information submitted by the applicant or requester at order creation
- Stored in the search metadata from the original request
- Used as the basis for verification and comparison
- Never modified during the verification process
verified*):
- Information confirmed through the verification process
- Extracted from employer/institution responses (email, voice, fax, or structured JSON inbound)
- Represents the ground truth according to the verifying organization
- May differ from provided fields (discrepancies)
discrepancy boolean:
false: All key fields match between provided and verified data, or differences are treated as non-critical under tenant comparison settings (including semanticmatch_with_discrepancyon fields such aspositionorlocation)true: One or more key fields differ beyond configured tolerances (e.g., date gaps outside calendar-day windows, salary beyond absolute/percent thresholds, critical title or name conflicts)
employmentVerification or educationVerification; its fields drive verificationResult.discrepancy and scalar fields in verificationResult.extractedData.
The comparison uses normalized values (case-insensitive, whitespace-trimmed, date formats normalized) to avoid false positives from formatting differences. Inbound responses are also checked against the applicant and the employment or education details supplied at order creation. These checks use tenant-level comparison settings (config.comparisonConfig), including calendar-day tolerance windows per date field, semantic equivalence checks for applicant name, job title, degree, major, and location proximity, and for employment salary optional absolute and percent thresholds: when both providedSalary and the verifier-extracted salary parse, they are normalized to an estimated annual USD equivalent and a salary conflict is raised only if the difference exceeds both thresholds; unparseable salary text falls back to normalized string comparison. Date and salary mismatch explanations include the raw expected and extracted values (salary reasons also include the computed annual amounts).
Default config.comparisonConfig values (used when a field is omitted or invalid on the tenant settings):
verificationResult.discrepancy, root-level employmentVerification.fields, and verification/action-required webhook status rather than removed applicantMismatches / applicantDiscrepancies objects.
JSON channel closeout: For terminal verification.completed events where channel is JSON, verificationResult.extractedData retains best-match scalar fields and _processedFromJson / _thirdPartyProvider. Comparison for the selected highest-confidence record lives on root-level employmentVerification.fields. Duplicate verification data is removed from nested verificationResult objects. verificationResult.discrepancy is the operational closeout flag — it may be false even when the selected record includes fields.position.status: "match_with_discrepancy". verificationResult.search includes nested order/applicant context; See submit-inbound-notification for the inbound vs closeout comparison model.
Why This Matters:
Discrepancies may indicate:
- Minor differences in how information is recorded (e.g., “Software Engineer” vs “Sr. Software Engineer”)
- Applicant inaccuracies (intentional or unintentional)
- Incomplete or outdated information from either source
- Data entry errors on either side
EmploymentVerification Object
Complete employment verification data with both the canonicalfields comparison object and backward-compatible flat
provided* / verified* fields.
Payload shape: All channels (EMAIL, FAX, VOICE, and JSON) use a fields object with per-field comparison metadata (type, status, expected, extracted, optional reason, and comparison with engine / similarity / distance). Legacy flat fields remain for existing consumers. The highest-confidence employmentVerification object appears at the data root in webhook payloads, not inside metadata.extractedData or verificationResult. Synchronous inbound API responses likewise return it at the response root.
Fields-based comparison (fields)
Used consistently across channels. Each key under fields compares order data (expected) to the verifier-extracted value (extracted).
Each field object has the following shape:
Backward-compatible flat fields
These fields mirror values infields.*.expected and fields.*.extracted. They remain available for clients using the original
verification-object contract; new comparison logic should use fields.
Provided fields
Verified and metadata fields
Example: Employment Verification (Email — synchronous inbound response)
Example: Employment Verification (Voice)
Example: Employment Verification (JSON channel)
JSON closeouts emit the highest-confidence root-levelemploymentVerification object with per-field comparison metadata in fields. verificationResult.extractedData retains scalar extraction for the selected record but omits duplicate verification data. verificationResult.discrepancy is the operational closeout flag; individual fields may still show match_with_discrepancy (for example fields.position) when semantic equivalence applies under tenant comparison settings. verificationResult.search includes nested order/applicant context.
employmentVerification.fields. In this example, verificationResult.discrepancy is false while employmentVerification.fields.position.status is match_with_discrepancy — use discrepancy for the overall closeout decision and fields.* for selected-record detail. verificationResult.search may include full nested order/applicant metadata;
EducationVerification Object
Complete education verification data with both the canonicalfields comparison object and backward-compatible flat
provided* / verified* fields.
Payload shape: All channels (EMAIL, FAX, VOICE, and JSON) use a fields object with per-field comparison metadata (type, status, expected, extracted, optional reason, and comparison with engine / similarity / distance). Legacy flat fields remain for existing consumers. The highest-confidence educationVerification object appears at the data root in webhook payloads, not inside metadata.extractedData or verificationResult. Synchronous inbound API responses likewise return it at the response root.
Fields-based comparison (fields)
Each key under fields compares order data (expected) to the verifier-extracted value (extracted).
Each field object has the following shape:
Backward-compatible flat fields
These fields mirror values infields.*.expected and fields.*.extracted. They remain available for clients using the original
verification-object contract; new comparison logic should use fields.
Provided fields
Verified and metadata fields
Example: Education Verification (Fax)
Example: Education Verification (No Record)
AccreditationResult Object
Included in education verification webhooks (verification.completed and verification.action_required).
Event: verification.action_required
Sent when verification cannot proceed without manual intervention.data.message is JSON-channel QA Review only. It appears on verification.action_required when channel is JSON, status is QA Review, and the inbound was processed from third-party JSON (metadata.extractedData._processedFromJson: true). EMAIL, FAX, and VOICE action-required webhooks do not include message — use metadata.summary and related context instead.For EMAIL/FAX/VOICE verified inbound with applicant or field mismatches, the typical path is verification.completed with verificationResult.discrepancy: true and per-field detail on root-level employmentVerification.fields, not verification.action_required.Data Fields
ActionRequiredReasonCode Enum
If
searchConfig.thirdPartyBan contains the detected canonical vendor, THIRD_PARTY_RECORD is intentionally suppressed.
Research records an audit notation and continues manual/outbound handling. Inbound third-party redirects to a banned vendor emit
HUMAN_ESCALATION with metadata.escalationSubtype: "THIRD_PARTY_BAN". See Third-party ban.Questionnaire-related metadata (HUMAN_ESCALATION)
When custom or standard questions drive escalation, inspect metadata.escalationSubtype (and related fields). These paths do not emit verification.completed.
Integration tips:
- Treat
MISSING_REQUIRED_INFORMATIONandVERIFICATION_DATA_INVALIDas form-quality failures — show ops the partial answers and signedartifactUrlswhen present. VOICE_ONLY_NOT_SUPPORTEDmeans the search has only a voice destination; provide an email or fax contact because voice outbound is not supported.invalidFields[].reasonis a human-readable string (for examplefewer than minSelections (1),value "Maybe" is not a defined option,multiple exclusive selections detected).
ContactPayload Object
Structured contact information based on search type.EmploymentContactPayload
EducationContactPayload
Example: THIRD_PARTY_RECORD (Employment)
Example: THIRD_PARTY_RECORD (Education with Accreditation)
Example: UPSTREAM_ISSUE
Example: SYSTEM_FAILURE
Example: SLA_REACHED
Example: HUMAN_ESCALATION (EMAIL — no message)
Non-JSON escalations (for example SLA timeout) may use status: "QA Review" but do not include data.message. Review context lives in metadata (for example summary, contactPlan).
Example: HUMAN_ESCALATION (JSON channel — includes message)
For JSON-channel QA Review (status: "QA Review"), includes a root-level message string intended for display to clients — operators, review queues, or end-user-facing UI. It is a plain-language summary of the issue and what the third-party provider returned, so reviewers do not need to parse structured JSON to understand the case.
The message is auto-generated when channel is JSON, normalized verification records are present, and the inbound payload was processed from third-party JSON (metadata.extractedData._processedFromJson: true). This applies regardless of reasonCode (for example HUMAN_ESCALATION or OTHER). It contains:
- Intro line — the review task (for example, choose the most relevant employment or education record and close the search using that row).
- One
Record Nblock per provider history row — extracted values such as employer/school, job title or degree, dates, location, applicant name, salary, and contact details when present. - Per-record
comments— short notes on notable issues (applicant name conflicts, missing fields, location mismatches, etc.).
employmentVerification / educationVerification field contains only the highest-confidence object. Message string values are double-quoted and escaped; numeric and boolean values are unquoted. When a rendered value comes from a field whose structured comparison has status: "mismatch", the value includes a display suffix of [Mismatch]. Use message for human-readable UI copy; use employmentVerification.fields / educationVerification.fields for programmatic field comparison and metadata.extractedData for scalar extraction and escalation flags.
Example: HUMAN_ESCALATION (missing required custom question)
Example: HUMAN_ESCALATION (invalid custom question marks)
Example: HUMAN_ESCALATION (voice only)
Emitted when the only available channel is voice — voice outbound is not supported.Example: OTHER
OTHER (JSON channel)
JSON-channel inbound responses routed withreasonCode: "OTHER" — for example when extraction yields no_record_or_not_found but the provider payload still contains employment or education history rows — include the same client-facing message field as other JSON-channel verification.action_required events. Display message in review UI so operators and end users see a plain-language summary of evaluated records and why manual review is needed; use employmentVerification.fields / educationVerification.fields for structured comparison and metadata.extractedData for scalar extraction flags.
OTHER (EMAIL channel)
Non-JSONOTHER webhooks omit message and typically carry review context in metadata alone. Verified EMAIL inbound with wrong applicant or title data (outcome verified with field mismatches) usually emits verification.completed with verificationResult.discrepancy: true, not verification.action_required — see Discrepancy Detection.
Event: verification.notification
Sent during intermediate stages of verification process.Data Fields
VerificationNotificationType Enum
Classification Object
ContactPlanEntry Object
CONTACT_PLAN is emitted after research discovers viable contacts. Contact changes through the public Manage Search Contacts endpoints use CONTACT_PLAN_ADDED, CONTACT_PLAN_UPDATED, or CONTACT_PLAN_REMOVED. Accuracy marks use CONTACT_PLAN_MARKED_ACCURATE or CONTACT_PLAN_MARKED_INACCURATE and include metadata.accuracyStatus plus metadata.reason.
When a contact mutation notification is emitted,
metadata may include:
When a
SEARCH_UPDATED notification is emitted, metadata includes:
Understanding Confidence Scores:
Confidence scores (0-1) indicate the system’s trust in the contact information:
0.9-1.0: High confidence - Previously verified contacts, organization preferences with strict language0.7-0.89: Medium-high confidence - Verified contacts without strict language, internal database matches0.5-0.69: Medium confidence - Web research results, parent organization contacts< 0.5: Lower confidence - Uncertain matches, limited informationnull: No confidence score available (third-party vendors, manual entries)
- Determine outbound attempt priority (higher confidence contacts attempted first)
- Apply minimum confidence thresholds per channel (configurable in organization policy)
- Filter out low-quality contacts before outbound attempts
OutboundAttempt Object
OutboundControl Object
Used for pause/resume notification types:OUTBOUND_PAUSED, OUTBOUNDS_PAUSED, OUTBOUND_RESUMED, and OUTBOUNDS_RESUMED.
OutboundControlItem Object
InboundMetadata Object
INBOUND_RECEIVED_DETAIL metadata object
Used whennotificationType is INBOUND_RECEIVED_DETAIL (all inbound channels return the highest-confidence root-level verification object).
JSON channel
extractedData highlights (under metadata.extractedData; the selected verification object is at data root):
_processedFromJson,_thirdPartyProvider— processing provenanceemployerName,jobTitle,startDate,endDate,employerLocation,extractedApplicantName,refId,searchType,outcome— best-match scalar extraction (same source asemploymentVerification)verifiedDates,verifiedContent, optionalextraction— structured extraction mirrors for backward compatibilityapplicantMismatch,_requiresManualReview— operational boolean flags when a critical mismatch on the best-match row triggers manual review
applicantMismatches and applicantDiscrepancies have been removed from webhook and inbound notification payloads. Use employmentVerification.fields / educationVerification.fields for selected-record field comparison detail.
Root-level verification object (data.employmentVerification / data.educationVerification):
- Normalized verification objects with per-field comparison metadata in
fields(type,status,expected,extracted,comparison.engine, optionalsimilarityordistance) JSONchannel: one element per provider history row, each withconfidence, sorted descending (see the Employment Verification (JSON channel) example at confidences0.94,0.22,0.05)- Fields such as
salary,rehire,reasonLeft, andcontactNumbermay benot_checkedwithcomparison.engine: "none"when order data has no comparable value - Omitted from
metadata.extractedDatato avoid duplication
notifications.includeRawInboundInWebhook: false to strip rawBody and extractedData from the delivered webhook (activity records retain full metadata).
Example: CONTACT_PLAN
Example: OUTBOUND_ATTEMPT
Example: INBOUND_MESSAGE (Email)
Example: INBOUND_MESSAGE (Voice)
Example: INBOUND_MESSAGE (Urgent)
Example: INBOUND_RECEIVED_DETAIL (JSON channel)
Emitted after a successfulchannel: "JSON" inbound notification when the best-match history row aligns with applicant-provided fields under tenant comparison settings. Top-level applicantMismatch is omitted; eventual verification.completed closeout may still set discrepancy: false when only non-critical field-level differences remain (for example fields.position.status: "match_with_discrepancy"). Normalized rows appear at data.employmentVerification, not inside metadata.extractedData.
Example: INBOUND_RECEIVED_DETAIL (JSON channel — discrepancies only)
Same webhook shape and search as above. The non-critical title difference appears onemploymentVerification.fields.position (status: "match_with_discrepancy") without applicantMismatch in metadata.extractedData. applicantDiscrepancies is not returned — use fields.* instead.
Example: INBOUND_RECEIVED_DETAIL (JSON channel — critical mismatch)
Emitted after a successfulchannel: "JSON" inbound notification. When a critical mismatch triggers escalation, metadata.extractedData may include applicantMismatch: true and _requiresManualReview: true. Field-level detail is on employmentVerification.fields — applicantMismatches and applicantDiscrepancies are not returned.
Third-Party Vendor Reference
WhenreasonCode is THIRD_PARTY_RECORD, the metadata object includes vendor information. These same canonical vendor codes are accepted by searchConfig.thirdPartyBan for per-search third-party suppression; UNKNOWN is not accepted in the ban list. See Third-party ban for client-facing examples.
Employment Vendors
Education Vendors
Delivery, retries, and testing
Retry policy, signature verification handlers, idempotency, and local testing tools (ngrok, webhook.site) are documented once in Webhook Integration. Organization-level webhook defaults: Webhook Integration — Organization-level configuration. SLA and escalation behavior: SLA and escalation.Footnotes
- “Theary” is the product/platform name reflected in this wire-level identifier. It refers to the same product as “SNH AI,” the company name used elsewhere in these docs. ↩

