Overview
RefAssured webhooks allow you to receive real-time notifications when specific events occur in the platform. Instead of polling the API for updates, webhooks push data to your application instantly when events happen.
Webhooks are HTTP callbacks that deliver event data to your specified endpoint URL. When a trigger event occurs in RefAssured, we send a POST request with a JSON payload to your configured endpoint.
| Benefits | Endpoint Requirements |
|---|---|
| Real-time updates — instant notifications when events occur Reduced API calls — no need to poll for status changes Event-driven architecture — build responsive integrations Lower latency — get data immediately without delays |
Accept POST requests with JSON payloads Respond with HTTP 200 within 5 seconds Publicly accessible (HTTPS strongly recommended) |
Webhook configuration
Follow these steps to configure webhooks in your RefAssured account.
|
1
|
Open Settings
From within RefAssured, click the Gear icon in the left sidebar to open your account settings.
|
|
2
|
Navigate to ATS Integration
In the settings navigation, select the ATS Integration tab.
|
|
3
|
Open Webhook Integration
Scroll down to find Webhook Integration and expand it to reveal the Webhook Configuration panel.
|
|
4
|
Configure endpoint URLs
Enter the endpoint URL for each webhook event type you want to receive.
|
|
5
|
Save your verification key
Your verification key is provided during setup. Store it securely — it's used to verify all incoming webhook requests.
|
|
6
|
Test each webhook
Use the Test button next to each webhook to send a sample payload and confirm your endpoint is responding correctly.
|
|
7
|
Enable webhooks
Toggle each webhook on to begin receiving live events.
|
Verification key
Your verification key is provided during setup and should be stored securely. It is used to verify that webhook requests are genuinely coming from RefAssured.
6d015479-0123-abcd-4567-e8ef634acf45
Security and verification
All webhook requests include the x-refassured-key header containing your verification key. Always verify this header before processing any webhook payload.
Node.js verification example
const express = require('express');
const app = express();
const REFASSURED_VERIFICATION_KEY = process.env.REFASSURED_WEBHOOK_KEY;
app.post('/webhooks/refassured', express.json(), (req, res) = {
// Verify the request is from RefAssured
const incomingKey = req.headers['x-refassured-key'];
if (incomingKey !== REFASSURED_VERIFICATION_KEY) {
console.error('Invalid verification key');
return res.status(401).send('Unauthorized');
}
// Respond quickly, then process async
res.status(200).send('OK');
processWebhookAsync(req.body);
});
Python verification example
from flask import Flask, request, jsonify
import os
app = Flask(__name__)
REFASSURED_VERIFICATION_KEY = os.environ.get('REFASSURED_WEBHOOK_KEY')
@app.route('/webhooks/refassured', methods=['POST'])
def handle_webhook():
incoming_key = request.headers.get('x-refassured-key')
if incoming_key != REFASSURED_VERIFICATION_KEY:
return jsonify({'error': 'Unauthorized'}), 401
process_webhook_async(request.json)
return jsonify({'status': 'received'}), 200
Security best practices
- Always verify the
x-refassured-keyheader before processing - Use HTTPS endpoints to encrypt data in transit
- Store the verification key securely and separately from your code
- Log all webhook events for debugging and auditing
Webhook types
RefAssured provides four webhook types across two categories.
| Webhook | Category | Trigger | Example URL |
|---|---|---|---|
| Sales Opt-ins | Opt-in Events | A reference opts in as a sales lead | yourdomain.com/webhooks/salesoptin |
| Candidate Opt-ins | Opt-in Events | A reference opts in as a candidate | yourdomain.com/webhooks/candidateoptin |
| Candidate Notes | Candidate Events | Notes written to candidate profiles | yourdomain.com/webhooks/candidatenotes |
| Reference Report Files | Candidate Events | Reference completed, report generated | yourdomain.com/webhooks/referencereports |
| Webhook | Description & Use Cases |
|---|---|
| Sales Opt-ins | Triggered when a reference opts in as a sales lead. Route qualified contacts directly into your CRM pipeline. Add qualified leads to your CRM • Trigger sales team notifications • Update lead scoring systems • Enrich contact databases |
| Candidate Opt-ins | Triggered when a reference opts in as a candidate. Automatically populate your talent pipeline. Add talent to your candidate pipeline • Notify recruiters of interested candidates • Update ATS with new candidate information • Trigger candidate nurturing workflows |
| Candidate Notes | Triggered when notes are written to candidate profiles — requests sent, references completed, IP collisions detected. Sync activity logs to your ATS • Update candidate timelines • Trigger notifications for status changes • Maintain audit trails |
| Reference Report Files | Triggered when a reference is completed and a report is generated. Delivers the full PDF as base64. Auto-attach reports to ATS profiles • Notify hiring managers of completed references • Archive reports in document management systems • Trigger next steps in hiring workflows |
Payload reference
Sales opt-in payload
Triggered when a reference opts in as a sales lead.
{
"isCandidateLead": true,
"isSalesLead": true,
"referenceRequestId": 10001,
"personId": 20001,
"companyName": "Acme Corp",
"personEmail": "jane.smith@example.com",
"personFirst": "Jane",
"personLast": "Smith",
"personMobile": "+1 6225555555",
"title": "Engineering Manager",
"candidateTitle": "Software Engineer",
"externalIdsMeta": {},
"ipGeolocation": {
"country": "United States",
"region": "California",
"city": "San Francisco",
"ip": "192.0.2.1"
},
"candidate": {
"personId": 30001,
"personFirst": "John",
"personLast": "Doe",
"personEmail": "john.doe@example.com",
"personMobile": "+1 6225555556",
"externalIds": {
"bullhornCandidateId": 40001,
"salesforceCandidateId": "003000000000001AAA"
}
}
}
| Field | Description |
|---|---|
isSalesLead: true |
Confirms this is a sales lead |
companyName |
Reference's current company |
title |
Reference's current job title |
candidateTitle |
Position they were referenced for |
ipGeolocation |
Geographic information from submission |
Candidate opt-in payload
Triggered when a reference opts in as a candidate.
{
"isCandidateLead": true,
"isSalesLead": true,
"referenceRequestId": 10001,
"personId": 20001,
"companyName": "Acme Corp",
"personEmail": "jane.smith@example.com",
"personFirst": "Jane",
"personLast": "Smith",
"personMobile": "+1 6225555555",
"title": "Engineering Manager",
"candidateTitle": "Software Engineer",
"externalIdsMeta": {},
"ipGeolocation": {
"country": "United States",
"region": "California",
"city": "San Francisco",
"ip": "192.0.2.1"
},
"candidate": {
"personId": 30001,
"personFirst": "John",
"personLast": "Doe",
"personEmail": "john.doe@example.com",
"personMobile": "+1 6225555556",
"externalIds": {
"bullhornCandidateId": 40001,
"salesforceCandidateId": "003000000000001AAA"
}
}
}
| Field | Description |
|---|---|
isCandidateLead: true |
Confirms this is a candidate lead |
personEmail / personFirst / personLast |
Contact information |
title |
Their current role |
ipGeolocation |
Location data for targeting |
Candidate notes payload
Triggered when notes are written to a candidate profile — including reference requests sent, references completed, and IP collision (fraud detection) events.
{
"referenceRequestId": 10001,
"candidate": {
"personId": 30001,
"personFirst": "John",
"personLast": "Doe",
"personEmail": "john.doe@example.com",
"personMobile": "+1 6225555556",
"externalIds": {
"bullhornCandidateId": 40001
}
},
"htmlNote": "Reference request sent in RefAssured for candidate John Doe...",
"textNote": "Reference request sent in RefAssured for candidate John Doe...",
"action": "RefAssured Sent"
}
| Field | Description |
|---|---|
externalIdsMeta |
Array of external system IDs (Salesforce, Bullhorn, etc.) |
htmlNote |
HTML-formatted note content |
textNote |
Plain text version of the note |
action |
"RefAssured Sent", "RefAssured Completed", or "IP Collision"
|
Reference report files payload
Triggered when a reference is completed and a report is generated. Delivers the full PDF as a base64-encoded string.
{
"candidate": {
"personId": 30001,
"personFirst": "John",
"personLast": "Doe",
"personEmail": "john.doe@example.com",
"personMobile": "+1 6225555556",
"externalIds": {
"bullhornCandidateId": 40001,
"salesforceCandidateId": "003000000000001AAA"
}
},
"file": {
"base64Content": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovTGlu...",
"fileType": "application/pdf",
"fileName": "RefAssured_PreHire_Reference_John_Doe_10001.pdf"
}
}
| Field | Description |
|---|---|
referenceRequestId |
Unique identifier for the reference request |
externalIdsMeta |
External system IDs for the candidate |
file.base64Content |
Base64-encoded PDF file content |
file.fileName |
Suggested filename for the report |
Processing the PDF
// Node.js
const fs = require('fs');
function processReferenceReport(payload) {
const { referenceRequestId, file } = payload;
// Decode base64 to buffer
const pdfBuffer = Buffer.from(file.base64Content, 'base64');
// Save to disk
fs.writeFileSync(`reports/${file.fileName}`, pdfBuffer);
// Or upload to cloud storage, attach to ATS, etc.
uploadToS3(file.fileName, pdfBuffer);
}
# Python
import base64
def process_reference_report(payload):
file_data = payload['file']
# Decode base64 content
pdf_content = base64.b64decode(file_data['base64Content'])
# Save to disk
with open(f"reports/{file_data['fileName']}", 'wb') as f:
f.write(pdf_content)
# Or upload to cloud storage, attach to ATS, etc.
upload_to_s3(file_data['fileName'], pdf_content)
Common use cases
async function handleReferenceReport(payload) {
const { externalIdsMeta, file } = payload;
const salesforceId = externalIdsMeta.salesforceCandidateId;
const pdfBuffer = Buffer.from(file.base64Content, 'base64');
await salesforceClient.sobject('Attachment').create({
Name: file.fileName,
Body: file.base64Content,
ParentId: salesforceId,
ContentType: 'application/pdf'
});
console.log(`Report attached to Salesforce candidate ${salesforceId}`);
}
async function handleSalesOptin(payload) {
const { personEmail, personFirst, personLast,
companyName, title, ipGeolocation } = payload;
await crmClient.createLead({
email: personEmail,
firstName: personFirst, lastName: personLast,
company: companyName, jobTitle: title,
leadSource: 'RefAssured Reference',
country: ipGeolocation.country,
state: ipGeolocation.region,
city: ipGeolocation.city
});
await sendSlackNotification(
`New sales lead: ${personFirst} ${personLast} at ${companyName}`
);
}
async function handleCandidateOptin(payload) {
const { personEmail, personFirst, personLast,
title, candidateTitle } = payload;
await atsClient.createCandidate({
email: personEmail,
firstName: personFirst, lastName: personLast,
currentTitle: title,
interestedIn: candidateTitle,
source: 'RefAssured Reference Opt-in',
status: 'Passive'
});
await marketingAutomation.addToList(personEmail, 'talent-pipeline');
}
async function handleCandidateNote(payload) {
const { externalIdsMeta, textNote, action } = payload;
if (!externalIdsMeta || externalIdsMeta.length === 0) return;
const salesforceId = externalIdsMeta[0].salesforceCandidateId;
await salesforceClient.sobject('Task').create({
WhoId: salesforceId,
Subject: `RefAssured: ${action}`,
Description: textNote,
Status: 'Completed',
ActivityDate: new Date().toISOString().split('T')[0]
});
}
Best practices
01 — Respond quickly
Always respond with HTTP 200 within 5 seconds, then process asynchronously. Long-running processing causes timeouts and lost events.
// Correct: respond first, then process
app.post('/webhook', (req, res) = {
res.status(200).send('OK');
processWebhookAsync(req.body);
});
// Incorrect: blocks response
app.post('/webhook', async (req, res) = {
await longRunningProcess(req.body); // Times out
res.status(200).send('OK');
});
02 — Log all events
Maintain audit logs for debugging and compliance. Include webhook type, full payload, received timestamp, and verification result.
await db.webhookLogs.create({
type: webhookType,
payload: payload,
receivedAt: new Date(),
verified: req.headers['x-refassured-key'] === VERIFICATION_KEY
});
03 — Monitor webhook health
Track success rates, response times, and failure counts. Send metrics to your monitoring system to catch issues early before they affect hiring workflows.
function trackWebhookMetrics(status) {
webhookMetrics.received++;
if (status === 'success')
webhookMetrics.processed++;
else
webhookMetrics.failed++;
metrics.gauge('webhooks.success_rate',
webhookMetrics.processed / webhookMetrics.received
);
}
04 — Use HTTPS endpoints
Always use HTTPS endpoints to encrypt data in transit. HTTP endpoints are not recommended for production environments handling candidate or lead data.
Troubleshooting
| Issue | What to Check |
|---|---|
| Not receiving events | Endpoint URL correct and publicly accessible; webhook toggle is on; firewall allows incoming POST; SSL cert is valid; server responds within 5 seconds. Use the Test button in settings to send a sample payload. |
| Verification failing | Correctly reading x-refassured-key header; key matches exactly with no extra spaces; using the correct key for your environment. |
| Timeout errors | Return HTTP 200 immediately and move all processing to a background queue or async handler. |
| Missing events | Webhook enabled for that event type; event occurred in RefAssured; endpoint returned 200 (failed responses are not retried); check webhook logs in RefAssured if available. |
Full Node.js implementation
A complete webhook handler covering all four event types — with verification, routing, error handling, and async processing.
const express = require('express');
const app = express();
const REFASSURED_VERIFICATION_KEY = process.env.REFASSURED_WEBHOOK_KEY;
app.post('/webhooks/refassured/:type', express.json(), async (req, res) = {
const webhookType = req.params.type;
// 1. Verify request
if (req.headers['x-refassured-key'] !== REFASSURED_VERIFICATION_KEY) {
console.error('Invalid verification key');
return res.status(401).send('Unauthorized');
}
// 2. Respond immediately
res.status(200).send('OK');
// 3. Process asynchronously
try {
const payload = req.body;
switch(webhookType) {
case 'sales-optin': await handleSalesOptin(payload); break;
case 'candidate-optin': await handleCandidateOptin(payload); break;
case 'candidate-notes': await handleCandidateNote(payload); break;
case 'reference-report': await handleReferenceReport(payload); break;
default: console.warn('Unknown webhook type:', webhookType);
}
} catch (error) {
console.error('Error processing webhook:', error);
await logWebhookError(webhookType, req.body, error);
}
});
async function handleSalesOptin(payload) {
console.log('Processing sales opt-in:', payload.personEmail);
}
async function handleCandidateOptin(payload) {
console.log('Processing candidate opt-in:', payload.personEmail);
}
async function handleCandidateNote(payload) {
console.log('Processing candidate note:', payload.action);
}
async function handleReferenceReport(payload) {
console.log('Processing reference report:', payload.referenceRequestId);
const pdfBuffer = Buffer.from(payload.file.base64Content, 'base64');
// Attach to ATS, upload to S3, etc.
}
app.listen(3000, () = console.log('Webhook server listening on port 3000'));
Support
| support@refassured.com | |
| Documentation | api-docs.refassured.com |