Event Webhooks
Webhooks allow external applications and ATS platforms to subscribe to real-time events in your XeCubes organization. When an event triggers, XeCubes delivers an HTTP POST request with a JSON payload to your configured webhook URL.
Supported Event Types​
| Event Name | Trigger Condition |
|---|---|
interview.scheduled | A candidate confirms an interview slot with Google Calendar or Zoom. |
interview.started | Candidate enters the WebRTC AI interview room. |
interview.completed | AI interview finishes, generating video recordings and raw transcripts. |
candidate.evaluated | Final AI evaluation scorecard, competency breakdown, and hire recommendation ready. |
assessment.submitted | Technical coding assessment submitted by candidate with test results. |
resume.parsed | Async resume batch parsing completes entity extraction. |
Webhook Payload Schema​
interview.scheduled Example​
{
"event_type": "interview.scheduled",
"data": {
"interviewId": "int_90J823K481",
"org_id": "org_92A838HDK8",
"job_id": "job_37H283J8K",
"candidate": {
"name": "Sarah Connor",
"email": "sarah.connor@example.com"
},
"start_time": "2026-06-12T15:00:00Z",
"end_time": "2026-06-12T15:30:00Z",
"meeting_link": "https://meet.google.com/xyz-qprs-tuv"
},
"created_at": "2026-06-05T12:00:00Z"
}
candidate.evaluated Example​
{
"event_type": "candidate.evaluated",
"data": {
"interview_id": "int_90J823K481",
"candidate_id": "cand_1829381928",
"job_id": "job_37H283J8K",
"overall_score": 92.5,
"recommendation": "STRONG_HIRE",
"summary": "Demonstrated deep expertise in distributed caching, Raft consensus, and Go concurrency. Clean code with 100% test pass rate.",
"report_url": "https://app.xecubes.com/reports/int_90J823K481"
}
}
Verifying Webhook Signatures​
Each webhook request contains an X-XeCubes-Signature header to verify authenticity and protect against replay attacks:
X-XeCubes-Signature: t=1717588800,v1=9f8d7c6b5a4e3f2...
Verification Implementation​
- Node.js / Express
- Python / Flask
import express from 'express';
import crypto from 'crypto';
const app = express();
const WEBHOOK_SECRET = process.env.XECUBES_WEBHOOK_SECRET!;
app.post('/webhooks/xecubes', express.raw({ type: 'application/json' }), (req, res) => {
const signatureHeader = req.headers['x-xecubes-signature'] as string;
const rawBody = req.body.toString();
// Extract timestamp and hash
const [tPart, vPart] = signatureHeader.split(',');
const timestamp = tPart.split('=')[1];
const signature = vPart.split('=')[1];
// Compute expected HMAC SHA256
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
if (signature !== expectedSignature) {
return res.status(400).send('Invalid signature');
}
const payload = JSON.parse(rawBody);
console.log(`Received verified event: ${payload.event_type}`);
// Return HTTP 200 promptly
res.status(200).json({ received: true });
});
from flask import Flask, request, abort
import hmac
import hashlib
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('XECUBES_WEBHOOK_SECRET', '').encode('utf-8')
@app.route('/webhooks/xecubes', methods=['POST'])
def xecubes_webhook():
sig_header = request.headers.get('X-XeCubes-Signature', '')
raw_payload = request.get_data()
parts = dict(x.split('=') for x in sig_header.split(','))
timestamp = parts.get('t', '')
provided_sig = parts.get('v1', '')
signed_payload = f"{timestamp}.".encode('utf-8') + raw_payload
expected_sig = hmac.new(WEBHOOK_SECRET, signed_payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(provided_sig, expected_sig):
abort(400, 'Invalid signature')
data = request.json
print(f"Event received: {data.get('event_type')}")
return '', 200
Delivery & Retry Policy​
- Your webhook endpoint must respond with an
HTTP 2xxwithin 10 seconds. - If your server returns an error (4xx or 5xx) or times out, XeCubes retries delivery with exponential backoff:
- Retry 1: 1 minute later
- Retry 2: 5 minutes later
- Retry 3: 30 minutes later
- Retry 4: 2 hours later
- Retry 5: 8 hours later