Skip to Content
Webhooks

Webhooks

Boooply sends webhook events to your endpoint when key moments occur during the interview lifecycle. Configure webhooks in your dashboard.

Events

EventWebhook KeyDescription
Transcript readyinterview.transcriptFull conversation transcript with speaker names and timestamps
Evaluation completeinterview.evaluationAI analysis with summary, scores, and recommendation
Scores availableinterview.scoresCandidate score breakdown (communication, technical, culture fit)
Recording availableinterview.recordingVideo 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 startMs and endMs. For AI interviews, only startMs is available.

Source fields (Platform API)

When interviews are created via a platform API key, all webhook payloads include source tracking fields:

FieldTypeDescription
sourcestring | nullThe ATS organization name that created this interview (e.g., “Acme Corp”)
sourceIdstring | nullThe ATS organization’s internal ID — use this to identify which of your customers the webhook belongs to
sourcePlatformstring | nullThe platform name (e.g., “AI TalentFlow”) — set when generating the platform key

For platform integrations: Use sourceId to route webhooks to the correct organization in your system. This is the organizationId you passed when calling createOrganizationApiKey(). These fields are null for 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" } }
FieldTypeDescription
overallRatingnumber1–5 scale
recommendationstringSTRONG_PASS | PASS | REVIEW | FAIL | STRONG_FAIL
scoresobjectBreakdown: overall, communication, technicalSkills, problemSolving, cultureFit, experienceMatch (0–100)
oneLineSummarystringOne-line TLDR
recruiterSummaryobjecttldr + quickDecision (ADVANCE/HOLD/REJECT)
skillsAssessmentarrayPer-skill ratings with evidence
strengthsAndWeaknessesobjectStrengths and weaknesses with evidence
redFlagsarrayConcerns with severity (CRITICAL/WARNING) and context
positiveSignalsarrayPositive indicators with context
questionsAskedarrayEach question, answer, quality score (1–5), topic, timestamp
sentimentAnalysisobjectEnthusiasm, authenticity, stress level
meetingStatisticsobjectSpeaking time %, response times
nextStepsobjectFollow-up questions and focus areas for next round
customQuestionResponsesarrayResponses to recruiter’s custom questions
ratingsForRecruitersobjectFormatted 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" } }
FieldTypeDescription
overallRatingnumber1–5 scale
recommendationstringSTRONG_PASS | PASS | REVIEW | FAIL | STRONG_FAIL
scoresobjectFull breakdown: overall, communication, technicalSkills, problemSolving, cultureFit, experienceMatch (0–100)
ratingsForRecruitersobjectFormatted for ATS pipeline import
oneLineSummarystringOne-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" } }
FieldTypeDescription
durationnumberRecording duration in seconds
formatstringFile format (e.g., mp4)
videoUrlstringPre-signed S3 URL — expires after 7 days
videoApiUrlstringStable API endpoint — generates a fresh download URL on each request (requires API key auth)

Tip: Store the videoApiUrl in your system. It always works and generates a fresh pre-signed URL on each request, while videoUrl expires after 7 days.


Headers

Every webhook request includes these headers:

HeaderDescriptionExample
Content-TypeAlways JSONapplication/json
X-Boooply-SignatureHMAC-SHA256 signature of the payloada1b2c3d4...
X-Boooply-EventEvent typeinterview.evaluation
X-Boooply-PlatformPlatform identifiercustom
X-Boooply-Meeting-IdMeeting code for this eventBoooply-AI-1234567890
AuthorizationBearer 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 2xx status 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 200 status within 30 seconds. Process data asynchronously if needed.
  • Verify signatures — Always validate the X-Boooply-Signature header 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 videoApiUrl instead of videoUrl since pre-signed URLs expire after 7 days.
Last updated on