How to Receive Webhooks
This guide explains how to set up a webhook receiver for CarvOS outgoing events.
Overview¶
CarvOS sends outgoing webhooks to notify your system when:
- Candidate, vacancy, or application data has been processed
- Meeting transcripts, summaries, or notes are ready
- A workspace has been provisioned or a member added
Create a Webhook Endpoint¶
Your endpoint must:
- Accept POST requests with JSON body
- Verify the signature before processing
- Return 2xx status within 5 seconds
Example: Python with FastAPI¶
from fastapi import FastAPI, Request, HTTPException, Header
import hmac
import hashlib
app = FastAPI()
WEBHOOK_SECRET = "your-outgoing-webhook-secret"
def verify_signature(payload: bytes, signature_header: str) -> bool:
"""Verify HMAC-SHA256 signature in t={timestamp},v1={signature} format."""
parts = dict(part.split("=", 1) for part in signature_header.split(","))
timestamp = parts["t"]
provided_sig = parts["v1"]
signed_payload = f"{timestamp}.".encode() + payload
expected = hmac.new(
WEBHOOK_SECRET.encode(),
signed_payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, provided_sig)
@app.post("/webhooks/carvos")
async def handle_webhook(
request: Request,
x_webhook_signature: str = Header(alias="X-Webhook-Signature")
):
payload = await request.body()
if not verify_signature(payload, x_webhook_signature):
raise HTTPException(status_code=401, detail="Invalid signature")
data = await request.json()
event_type = data.get("event")
# Process the event
if event_type == "candidate.created":
# Handle candidate data
pass
elif event_type == "meeting.transcript_ready":
# Handle meeting transcript
pass
return {"status": "received"}
Signature Verification¶
Every outgoing webhook includes an X-Webhook-Signature header:
The signature is computed as HMAC-SHA256(key=secret, message="{timestamp}.{body}").
Warning
Always verify this signature before processing the webhook payload. Use your outgoing webhook secret — not the incoming webhook secret.
Outgoing Event Types¶
Entity Events¶
| Event | Description |
|---|---|
candidate.created |
Candidate profile created and normalized |
candidate.updated |
Candidate data updated |
candidate.deleted |
Candidate deleted |
application.created |
Application created |
application.updated |
Application updated |
application.deleted |
Application deleted |
vacancy.created |
Vacancy created and normalized |
vacancy.updated |
Vacancy updated |
vacancy.deleted |
Vacancy deleted |
Meeting Events¶
| Event | Description |
|---|---|
meeting.transcript_ready |
Meeting transcript is available |
meeting.summary_ready |
AI-generated meeting summary is complete |
meeting.notes_ready |
AI-generated meeting notes are complete |
Workspace Events¶
| Event | Description |
|---|---|
workspace.provisioned |
Workspace has been provisioned |
workspace.member_added |
Member added to workspace |
Payload Envelope¶
All outgoing webhooks share a common envelope:
{
"event": "candidate.created",
"event_id": "evt_abc123",
"client_id": "your-client-id",
"timestamp": "2026-01-15T10:30:00Z",
"status": "success",
"error": null,
"data": { }
}
| Field | Type | Description |
|---|---|---|
event |
string | Event type identifier |
event_id |
string | Unique event identifier for idempotency |
client_id |
string | Your ATS client identifier |
timestamp |
string | ISO 8601 timestamp |
status |
string | "success" or "failure" |
error |
object | null | Error detail on failure (null on success) |
data |
object | null | Event-specific payload (null on failure) |
Retry Policy¶
If your endpoint returns a non-2xx status, CarvOS retries with exponential backoff up to 5 attempts before routing to the dead letter queue.
Testing Webhooks¶
Use the webhook sandbox to send test events to your endpoint before going live.
Next Steps¶
-
Meeting Intelligence
Get transcripts, summaries, and notes from meetings
-
Webhook Sandbox
Test your webhook integration