Skip to content

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:

  1. Accept POST requests with JSON body
  2. Verify the signature before processing
  3. 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:

X-Webhook-Signature: t=1234567890,v1=abc123...

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