Skip to main content

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 with searchId:
  • externalSearchId: Your external identifier provided at order creation
For terminal events (verification.completed, verification.action_required):
  • channel: Single terminal channel used (EMAIL, VOICE, FAX, or JSON)
  • Root-level highest-confidence employmentVerification or educationVerification objects containing both per-field comparison in fields and backward-compatible flat provided* / verified* properties — not nested in verificationResult or metadata.extractedData
  • message (JSON-channel verification.action_required with status: "QA Review" only): Human-readable review summary for client UI — plain-text breakdown of each third-party history record when _processedFromJson is true. Not present on EMAIL, FAX, or VOICE action-required webhooks (including status: "QA Review"). EMAIL/FAX/VOICE verified inbound with field conflicts typically close via verification.completed and verificationResult.discrepancy: true instead.
  • discrepancy (on verification.completed only): Operational closeout flag on verificationResult; may be false when the best-match row only has non-critical match_with_discrepancy field statuses under tenant comparison settings
For inbound detail notifications (verification.notification with INBOUND_RECEIVED_DETAIL):
  • Root-level employmentVerification or educationVerification when normalized rows are available, with both fields comparison metadata and backward-compatible flat properties
  • Scalar extraction under metadata.extractedData without duplicate verification objects or removed applicantMismatches / applicantDiscrepancies objects
  • Optional applicantMismatch / _requiresManualReview boolean 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.
For EDUCATION searches:
  • 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 send verificationQuestions 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 Fields (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 Detection: For VERIFIED outcomes, the system automatically compares key fields and sets the discrepancy boolean:
  • false: All key fields match between provided and verified data, or differences are treated as non-critical under tenant comparison settings (including semantic match_with_discrepancy on fields such as position or location)
  • 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)
Provider payloads can contain multiple records internally. External payloads expose only the highest-confidence record as 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):
When values are semantically similar above the configured similarity threshold, the response can still be treated as a match even if the exact strings differ (for example, equivalent degree names or job-title variants). Critical conflicts are reflected through the normal verification review and discrepancy flow; clients should rely on 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
Your system should review discrepancies and decide how to handle them based on your business rules.

EmploymentVerification Object

Complete employment verification data with both the canonical fields 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 in fields.*.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-level employmentVerification 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.
Note: Per-field outcomes for the selected highest-confidence record live on 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 canonical fields 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 in fields.*.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.
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_INFORMATION and VERIFICATION_DATA_INVALID as form-quality failures — show ops the partial answers and signed artifactUrls when present.
  • VOICE_ONLY_NOT_SUPPORTED means the search has only a voice destination; provide an email or fax contact because voice outbound is not supported.
  • invalidFields[].reason is a human-readable string (for example fewer than minSelections (1), value "Maybe" is not a defined option, multiple exclusive selections detected).
Full request/response shapes and channel selection: Verification question.

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:
  1. Intro line — the review task (for example, choose the most relevant employment or education record and close the search using that row).
  2. One Record N block per provider history row — extracted values such as employer/school, job title or degree, dates, location, applicant name, salary, and contact details when present.
  3. Per-record comments — short notes on notable issues (applicant name conflicts, missing fields, location mismatches, etc.).
The message can list internally evaluated records in confidence order (highest first), while the structured root-level 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 with reasonCode: "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-JSON OTHER 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 language
  • 0.7-0.89: Medium-high confidence - Verified contacts without strict language, internal database matches
  • 0.5-0.69: Medium confidence - Web research results, parent organization contacts
  • < 0.5: Lower confidence - Uncertain matches, limited information
  • null: No confidence score available (third-party vendors, manual entries)
The system uses confidence scores to:
  • 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 when notificationType 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 provenance
  • employerName, jobTitle, startDate, endDate, employerLocation, extractedApplicantName, refId, searchType, outcome — best-match scalar extraction (same source as employmentVerification)
  • verifiedDates, verifiedContent, optional extraction — structured extraction mirrors for backward compatibility
  • applicantMismatch, _requiresManualReview — operational boolean flags when a critical mismatch on the best-match row triggers manual review
Not included: 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, optional similarity or distance)
  • JSON channel: one element per provider history row, each with confidence, sorted descending (see the Employment Verification (JSON channel) example at confidences 0.94, 0.22, 0.05)
  • Fields such as salary, rehire, reasonLeft, and contactNumber may be not_checked with comparison.engine: "none" when order data has no comparable value
  • Omitted from metadata.extractedData to avoid duplication
Per-search policy may set 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 successful channel: "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 on employmentVerification.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 successful channel: "JSON" inbound notification. When a critical mismatch triggers escalation, metadata.extractedData may include applicantMismatch: true and _requiresManualReview: true. Field-level detail is on employmentVerification.fieldsapplicantMismatches and applicantDiscrepancies are not returned.

Third-Party Vendor Reference

When reasonCode 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

  1. “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.