Skip to main content

Webhook System Overview

Theary’s webhook system provides real-time notifications throughout the verification lifecycle. This guide explains how webhooks fit into the overall verification workflow and when different webhook events are triggered.

Verification Lifecycle

The verification workflow consists of several phases, each triggering specific webhook events:

Webhook Event Types

Theary sends three types of webhook events:

1. verification.completed

When it’s sent:
  • Verification reaches a terminal outcome (VERIFIED, NO_RECORD, WRONG_ORG, THIRD_PARTY)
  • All verification data has been processed and validated
  • Results are ready for consumption by your system
What it contains:
  • Complete verification result with all extracted data
  • Full employment or education verification object (provided vs verified fields)
  • Discrepancy flag indicating if verified data differs from provided data
  • Research decision log (audit trail of contact discovery)
  • Education accreditation data (for EDUCATION searches)
  • Terminal channel used (EMAIL, VOICE, or FAX)
Use this to:
  • Update your applicant records with verified data
  • Mark verifications as complete in your system
  • Trigger downstream workflows (e.g., send results to hiring manager)
  • Generate reports or analytics

2. verification.action_required

When it’s sent:
  • Verification cannot proceed without manual intervention
  • Third-party vendor is required (THIRD_PARTY_RECORD)
  • External service failure (UPSTREAM_ISSUE)
  • Internal system error (SYSTEM_FAILURE)
  • SLA timeout exceeded (SLA_REACHED)
  • All automation exhausted (HUMAN_ESCALATION)
What it contains:
  • Reason code explaining why action is required
  • Structured contact payload (employment, education, license, or reference)
  • Metadata with troubleshooting context
  • Research decision log (for HUMAN_ESCALATION)
  • Contact plan data (when escalating with research results)
Use this to:
  • Alert your team or end-user about the issue
  • Route to appropriate handler based on reason code
  • Guide users to next steps (e.g., submit via third-party portal)
  • Track verifications requiring intervention

3. verification.notification

When it’s sent:
  • Intermediate stages during the verification process
  • Research completes and generates a contact plan
  • Outbound attempt is made (email sent, call initiated, fax dispatched)
  • Inbound response received (email reply, voicemail, fax received)
What it contains:
  • Notification type (CONTACT_PLAN, OUTBOUND_ATTEMPT, INBOUND_MESSAGE)
  • Contact plan with discovered contacts and confidence scores
  • Outbound attempt metadata (channel, destination, attempt number)
  • Inbound message classification and summary
Use this to:
  • Update UI in real-time with verification progress
  • Show contact attempts to end users
  • Track verification velocity and bottlenecks
  • Log activity for compliance and audit purposes

Webhook Configuration

Webhooks can be configured at two levels:

Tenant-Level Configuration

Global webhook settings for your organization, stored in your tenant configuration. Contact your Theary account manager to configure tenant-level webhooks. Benefits:
  • Applies to all verifications automatically
  • Centralized management
  • No per-request configuration needed

Request-Level Configuration

Per-verification webhook settings via the webhookConfig object in the order creation request. Benefits:
  • Override tenant defaults for specific orders
  • Route different verification types to different endpoints
  • Apply custom headers or authentication per order
Example:
{
  "webhookConfig": {
    "enabled": true,
    "secret": "your-webhook-secret",
    "retryAttempts": 3,
    "closeoutEndpoints": {
      "EMPLOYMENT": [
        {
          "url": "https://your-app.com/webhooks/employment",
          "events": ["verification.completed"]
        }
      ],
      "EDUCATION": [
        {
          "url": "https://your-app.com/webhooks/education",
          "events": ["verification.completed", "verification.notification"]
        }
      ]
    },
    "fallbackEndpoint": [
      {
        "url": "https://your-app.com/webhooks/all-events",
        "events": ["verification.action_required"],
        "searchTypes": ["EMPLOYMENT", "EDUCATION"]
      }
    ]
  }
}

Webhook Routing

The API resolves webhook endpoints using the following priority:
  1. Search-specific endpoint: If configured for the search type (e.g., EMPLOYMENT), that endpoint is used
  2. Fallback endpoint: If no search-specific endpoint is configured, the fallback endpoint is used
  3. Event filtering: Each target can specify which events it wants to receive (events array)
  4. Search type filtering: Fallback targets can specify which search types they handle (searchTypes array)
When multiple targets match an event, the API delivers to all matching targets concurrently.

Webhook Security

All webhooks are signed using HMAC-SHA256 for authenticity verification: Headers:
  • X-Webhook-Signature: sha256=<hex> format
  • X-Event-Id: Unique delivery ID for idempotency
  • X-Event-Type: Event type (verification.completed, etc.)
  • X-Search-Type: Search type (EMPLOYMENT, EDUCATION, etc.)
  • X-External-Search-Id: Your external identifier (if provided)
Signature Verification:
const crypto = require('crypto')

function verifyWebhookSignature(rawBody, signature, secret) {
  const signatureValue = signature.replace('sha256=', '')
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signatureValue, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  )
}
Important: Always verify using the raw request body bytes, not parsed JSON.

Webhook Reliability

The system ensures reliable webhook delivery through: Retry Logic:
  • Configurable retry attempts (default: 3, max: 10)
  • Exponential backoff with jitter (2s, 4s, 8s)
  • Client errors (4xx) are not retried
  • Server errors (5xx) are retried automatically
Idempotency:
  • Each delivery has a unique X-Event-Id
  • Use this header to detect and handle duplicate deliveries
  • Webhooks may be retried, so ensure your handlers are idempotent
Persistence:
  • All webhook deliveries are logged in the database
  • Delivery attempts are tracked with status codes and errors
  • Audit trail available for troubleshooting
Timeouts:
  • Per-request timeout: 30 seconds
  • Your endpoint must respond with 2xx within this time
  • Heavy processing should be done asynchronously

Research Decision Log

For verifications that complete research (contact discovery), webhooks include a researchDecisionLog object that provides full transparency into how contacts were found:
{
  "researchDecisionLog": {
    "timestamp": "2025-12-09T10:30:00Z",
    "organizationName": "Acme Corporation",
    "location": "San Francisco, CA",
    "decisions": [
      {
        "stage": "ORG_PREFERENCE_SEARCH",
        "decision": "PROCEED",
        "reasoning": "Found 2 contacts from organization preference table",
        "confidence": 0.95,
        "timestamp": "2025-12-09T10:30:01Z"
      },
      {
        "stage": "WEB_SEARCH",
        "decision": "SKIP",
        "reasoning": "Sufficient contacts from org preferences; web search not needed",
        "timestamp": "2025-12-09T10:30:02Z"
      }
    ]
  }
}
Research Stages:
  • ORG_PREFERENCE_SEARCH: Organization-specific preferences and contacts
  • VERIFIED_CONTACTS_SEARCH: Previously successful contacts
  • INTERNAL_SEARCH: Internal Algolia knowledge base
  • PARENT_ORG_SEARCH: Parent organization research
  • WEB_SEARCH: OpenAI web research for contact discovery
  • CHANNEL_POLICY_CHECK: Channel policy validation
  • FINAL_RESULT: Final research outcome

Education Accreditation

For EDUCATION searches, completed and action_required webhooks include accreditation data:
{
  "accreditation": {
    "reference": {
      "name": "Stanford University",
      "date": "2022-06-12",
      "city": "Stanford",
      "state": "CA"
    },
    "matches": [
      {
        "campus": {
          "dapipId": "243744",
          "locationName": "Stanford University",
          "city": "Stanford",
          "state": "CA",
          "url": "https://www.stanford.edu"
        },
        "accreditation": {
          "statusLabel": "Accredited",
          "isAccredited": true,
          "accreditor": "WASC Senior College and University Commission",
          "validFrom": "1949-01-01",
          "validThrough": null,
          "reason": "Accredited from 1949-01-01 through present"
        }
      }
    ],
    "metadata": {
      "accreditationStatus": "accredited",
      "campusesConsidered": 1,
      "dataSource": "DAPIP Database",
      "checkedOn": "2025-12-02"
    }
  }
}
The system automatically:
  • Looks up institutions in the DAPIP (Database of Accredited Postsecondary Institutions) database
  • Checks accreditation status at the reference date (graduation or attendance)
  • Provides campus details, accreditor information, and historical timeline
  • Suggests alternate campuses when exact match is not found

Best Practices

1. Acknowledge Quickly

Respond with HTTP 200 within 30 seconds and process asynchronously:
app.post('/webhooks', async (req, res) => {
  res.status(200).send('OK')
  processWebhook(req.body).catch(console.error)
})

2. Verify Signatures

Always verify HMAC signatures before processing:
const signature = req.headers['x-webhook-signature']
if (!verifyWebhookSignature(req.rawBody, signature, secret)) {
  return res.status(401).send('Invalid signature')
}

3. Handle Idempotency

Use X-Event-Id to detect duplicates:
const eventId = req.headers['x-event-id']
if (await isProcessed(eventId)) {
  return res.status(200).send('Already processed')
}

4. Handle All Event Types

Implement handlers for all three event types:
switch (event) {
  case 'verification.completed':
    handleCompleted(data)
    break
  case 'verification.action_required':
    handleActionRequired(data)
    break
  case 'verification.notification':
    handleNotification(data)
    break
}

5. Use HTTPS

Always use HTTPS endpoints. The API only sends to HTTPS URLs in production.

6. Monitor Delivery

Track webhook delivery status and set up alerts for failed deliveries.

Next Steps