Webhooks
Boooply sends webhook events to your endpoint when key moments occur during the interview lifecycle. Configure webhooks in your dashboard.
Events
| Event | Webhook Key | Description |
|---|---|---|
| Transcript ready | interview.transcript | Full conversation transcript with speaker names and timestamps |
| Evaluation complete | interview.evaluation | AI analysis with summary, scores, and recommendation |
| Scores available | interview.scores | Candidate score breakdown (communication, technical, culture fit) |
| Recording available | interview.recording | Video recording ready with download URLs |
Note: Events are prefixed with
interview.in the webhook payload and headers. When configuring events in the dashboard, use the short key (e.g.,transcript).
Payload format
Every webhook POST request contains a JSON body with the event type and event-specific data.
interview.transcript
Fired after transcription completes (Whisper for human/team, real-time for AI interviews).
{
"event": "interview.transcript",
"data": {
"meetingCode": "Boooply-AI-1234567890",
"title": "Senior Frontend Engineer Interview",
"meetingType": "AI_ONLY",
"isTeamMeeting": false,
"candidateName": "Jane Doe",
"jobRole": "Senior Frontend Engineer",
"source": "Acme Corp",
"sourceId": "org_7234567890",
"sourcePlatform": "AI TalentFlow",
"transcript": [
{
"speaker": "AI Interviewer",
"text": "Tell me about your experience with React.",
"startMs": 12400
},
{
"speaker": "Jane Doe",
"text": "I've been working with React for 5 years...",
"startMs": 15200,
"endMs": 28900
}
],
"transcriptCount": 24,
"timestamp": "2026-03-21T18:30:00.000Z"
}
}For human/team meetings, each transcript entry includes both
startMsandendMs. For AI interviews, onlystartMsis available.
Source fields (Platform API)
When interviews are created via a platform API key, all webhook payloads include source tracking fields:
| Field | Type | Description |
|---|---|---|
source | string | null | The ATS organization name that created this interview (e.g., “Acme Corp”) |
sourceId | string | null | The ATS organization’s internal ID — use this to identify which of your customers the webhook belongs to |
sourcePlatform | string | null | The platform name (e.g., “AI TalentFlow”) — set when generating the platform key |
For platform integrations: Use
sourceIdto route webhooks to the correct organization in your system. This is theorganizationIdyou passed when callingcreateOrganizationApiKey(). These fields arenullfor interviews created directly from the Boooply dashboard.
interview.evaluation
Fired after AI analysis completes. Includes the full evaluation — scores, skills, strengths, weaknesses, red flags, questions asked, and recruiter recommendations.
{
"event": "interview.evaluation",
"data": {
"meetingCode": "Boooply-AI-1234567890",
"title": "Senior Frontend Engineer Interview",
"meetingType": "AI_ONLY",
"candidateName": "Jane Doe",
"jobRole": "Senior Frontend Engineer",
"source": "Acme Corp",
"sourceId": "org_7234567890",
"sourcePlatform": "AI TalentFlow",
"summary": "Jane demonstrated strong technical skills in React and TypeScript...",
"oneLineSummary": "Strong React skills but needs improvement in system design",
"overallRating": 3,
"recommendation": "PASS",
"confidenceLevel": "HIGH",
"scores": {
"overall": 72,
"communication": 80,
"technicalSkills": 70,
"problemSolving": 65,
"cultureFit": 85,
"experienceMatch": 70
},
"recruiterSummary": { "tldr": "Solid mid-level React developer", "quickDecision": "ADVANCE" },
"skillsAssessment": [
{ "skill": "React", "rating": 4, "evidence": "Explained hooks and context API well" }
],
"strengthsAndWeaknesses": {
"strengths": [{ "point": "Strong React fundamentals", "evidence": "..." }],
"weaknesses": [{ "point": "Limited system design", "evidence": "..." }]
},
"redFlags": [{ "flag": "Vague on past projects", "severity": "WARNING", "context": "..." }],
"positiveSignals": [{ "signal": "Enthusiastic about the role", "context": "..." }],
"sentimentAnalysis": { "enthusiasm": "HIGH", "authenticity": "GENUINE", "stressLevel": "LOW" },
"questionsAsked": [
{ "topic": "Technical", "question": "How do you handle state?", "candidateAnswer": "...", "answerQuality": 4, "timestamp": "02:15" }
],
"nextSteps": {
"recommendation": "Proceed to technical round",
"questionsToAsk": ["Describe a system you designed"],
"focusAreasForNextRound": ["System design", "TypeScript generics"]
},
"ratingsForRecruiters": { "technicalSkills": 70, "communication": 80, "cultureFit": 85, "overall": 72 },
"timestamp": "2026-03-21T18:35:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
overallRating | number | 1–5 scale |
recommendation | string | STRONG_PASS | PASS | REVIEW | FAIL | STRONG_FAIL |
scores | object | Breakdown: overall, communication, technicalSkills, problemSolving, cultureFit, experienceMatch (0–100) |
oneLineSummary | string | One-line TLDR |
recruiterSummary | object | tldr + quickDecision (ADVANCE/HOLD/REJECT) |
skillsAssessment | array | Per-skill ratings with evidence |
strengthsAndWeaknesses | object | Strengths and weaknesses with evidence |
redFlags | array | Concerns with severity (CRITICAL/WARNING) and context |
positiveSignals | array | Positive indicators with context |
questionsAsked | array | Each question, answer, quality score (1–5), topic, timestamp |
sentimentAnalysis | object | Enthusiasm, authenticity, stress level |
meetingStatistics | object | Speaking time %, response times |
nextSteps | object | Follow-up questions and focus areas for next round |
customQuestionResponses | array | Responses to recruiter’s custom questions |
ratingsForRecruiters | object | Formatted scores for ATS import |
interview.scores
Fired alongside evaluation — a lightweight payload with just the numerical scores. Useful if you only need numbers for your ATS or dashboard.
{
"event": "interview.scores",
"data": {
"meetingCode": "Boooply-AI-1234567890",
"candidateName": "Jane Doe",
"jobRole": "Senior Frontend Engineer",
"source": "Acme Corp",
"sourceId": "org_7234567890",
"sourcePlatform": "AI TalentFlow",
"overallRating": 3,
"recommendation": "PASS",
"confidenceLevel": "HIGH",
"scores": {
"overall": 72,
"communication": 80,
"technicalSkills": 70,
"problemSolving": 65,
"cultureFit": 85,
"experienceMatch": 70
},
"ratingsForRecruiters": { "technicalSkills": 70, "communication": 80, "cultureFit": 85, "overall": 72 },
"oneLineSummary": "Strong React skills but needs improvement in system design",
"timestamp": "2026-03-21T18:35:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
overallRating | number | 1–5 scale |
recommendation | string | STRONG_PASS | PASS | REVIEW | FAIL | STRONG_FAIL |
scores | object | Full breakdown: overall, communication, technicalSkills, problemSolving, cultureFit, experienceMatch (0–100) |
ratingsForRecruiters | object | Formatted for ATS pipeline import |
oneLineSummary | string | One-line TLDR |
interview.recording
Fired when the video recording has been processed and uploaded. Includes two URLs:
{
"event": "interview.recording",
"data": {
"meetingCode": "Boooply-AI-1234567890",
"title": "Senior Frontend Engineer Interview",
"source": "Acme Corp",
"sourceId": "org_7234567890",
"sourcePlatform": "AI TalentFlow",
"duration": 1234,
"format": "mp4",
"videoUrl": "https://s3.amazonaws.com/...?X-Amz-Expires=604800&...",
"videoApiUrl": "https://api.meetings.boooply.com/api/integration/interviews/Boooply-AI-1234567890/recording",
"timestamp": "2026-03-21T18:40:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
duration | number | Recording duration in seconds |
format | string | File format (e.g., mp4) |
videoUrl | string | Pre-signed S3 URL — expires after 7 days |
videoApiUrl | string | Stable API endpoint — generates a fresh download URL on each request (requires API key auth) |
Tip: Store the
videoApiUrlin your system. It always works and generates a fresh pre-signed URL on each request, whilevideoUrlexpires after 7 days.
Headers
Every webhook request includes these headers:
| Header | Description | Example |
|---|---|---|
Content-Type | Always JSON | application/json |
X-Boooply-Signature | HMAC-SHA256 signature of the payload | a1b2c3d4... |
X-Boooply-Event | Event type | interview.evaluation |
X-Boooply-Platform | Platform identifier | custom |
X-Boooply-Meeting-Id | Meeting code for this event | Boooply-AI-1234567890 |
Authorization | Bearer token (your webhook secret) | Bearer whsec_... |
Verifying signatures
Every webhook includes an X-Boooply-Signature header containing an HMAC-SHA256 signature of the JSON payload, signed with your webhook secret.
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express example
app.post('/webhooks/boooply', express.json(), (req, res) => {
const signature = req.headers['x-boooply-signature'];
const isValid = verifyWebhookSignature(req.body, signature, process.env.BOOOPLY_WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
switch (event) {
case 'interview.evaluation':
console.log(`Score: ${data.overallRating}/100 — ${data.recommendation}`);
console.log(`Organization: ${data.sourceId}`); // your org ID
break;
case 'interview.transcript':
console.log(`Transcript: ${data.transcriptCount} messages`);
break;
case 'interview.scores':
console.log(`Technical: ${data.technical}, Communication: ${data.communication}`);
break;
case 'interview.recording':
console.log(`Recording: ${data.videoApiUrl}`);
break;
}
res.status(200).send('OK');
});Python
import hmac
import hashlib
import json
def verify_webhook_signature(payload: dict, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
json.dumps(payload, separators=(',', ':')).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Retry policy
Failed webhooks are retried automatically with exponential backoff:
- 5 attempts total
- Backoff: 5s → 10s → 20s → 40s → 80s
- Only
2xxstatus codes are treated as successful - After all retries fail, the delivery is marked as failed
You can view delivery history and retry failed webhooks from the dashboard.
Best practices
- Respond quickly — Return a
200status within 30 seconds. Process data asynchronously if needed. - Verify signatures — Always validate the
X-Boooply-Signatureheader to ensure payloads haven’t been tampered with. - Handle duplicates — Use the
meetingCode+ event type as an idempotency key. In rare cases, the same event may be delivered more than once. - Use the stable URL — For recordings, store
videoApiUrlinstead ofvideoUrlsince pre-signed URLs expire after 7 days.