Overview
Webhooks deliver real-time HTTP POST notifications to your server when events occur in Celper AI. Instead of polling the API for changes, you register a URL and the system pushes updates to you as they happen.
Each company can register up to 5 webhooks. Each webhook can subscribe to one or more event types and optionally filter by candidate status.
Events
| Event | Description |
|---|---|
analysis_ready | Interview analysis completed for a candidate |
candidate_status_changed | A candidate’s status was updated |
cv_analysis_complete | CV scoring and AI evaluation finished |
bulk_import_complete | A bulk import job has finished processing |
Delivery Format
When an event fires, the API sends an HTTP POST request to your webhook URL with a JSON body:
{
"event": "candidate_status_changed",
"webhookId": "wh_abc123",
"companyId": "comp_xyz",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"candidateId": "cand_123",
"jobPositionId": "pos_456",
"status": "interviewed",
"previousStatus": "invited"
}
}| Field | Description |
|---|---|
event | The event type that triggered this delivery. |
webhookId | The ID of the webhook subscription that matched this event. |
companyId | Your company ID. |
timestamp | ISO 8601 timestamp of when the event occurred. |
data | Event-specific payload. Structure varies by event type. |
Signature Verification
Every webhook delivery is signed with HMAC-SHA256 so you can verify it came from Celper AI and was not tampered with in transit.
Each delivery includes two headers:
| Header | Description |
|---|---|
X-Celper-Signature | The signature in the format sha256=<hex digest>. |
X-Celper-Timestamp | Unix timestamp (seconds) of when the delivery was signed. |
The signing input is {timestamp}.{raw request body} — the timestamp and body joined by a period. This ensures each delivery has a unique signature even when the payload is identical.
Replay protection: Reject any delivery whose timestamp is more than 5 minutes (300 seconds) from your server’s current time.
Always verify the signature before processing a webhook delivery.
# Signature verification is done server-side, not with curl.
# When testing with curl, you can inspect the headers:
curl -X POST https://your-server.com/webhooks/celper -H "Content-Type: application/json" -H "X-Celper-Signature: sha256=a1b2c3d4..." -H "X-Celper-Timestamp: 1712825400" -d '{"event":"candidate_status_changed","data":{...}}'
# To verify:
# 1. Build the signing input: "{timestamp}.{body}"
# 2. Compute HMAC-SHA256 using your webhook secret
# 3. Compare with the X-Celper-Signature header value
# 4. Reject if timestamp is more than 300 seconds from nowRetry Policy
If your endpoint does not respond with a 2xx status code within 5 seconds, the delivery is considered failed and will be retried.
| Attempt | Delay after failure |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 2 minutes |
| 3rd retry | 10 minutes |
| 4th retry | 30 minutes |
| 5th retry | 2 hours |
After 5 failed delivery attempts, the delivery status is set to "exhausted" and no further retries are made for that specific event.
If a webhook accumulates 10 consecutive failed deliveries (across any events), it is automatically disabled. You can re-enable it from the API Management dashboard or via the API after fixing the issue.
Status Filters
When creating or updating a webhook, you can specify an optional statusFilters array to only receive candidate_status_changed events for specific statuses.
Valid status values:
new | invited | interviewed | selected | rejected | expired | failed | unevaluable
For example, setting statusFilters: ["selected", "rejected"] means you will only receive candidate_status_changed events when a candidate moves to the selected or rejected status. All other status transitions are silently skipped.
If statusFilters is omitted or empty, you receive events for all status changes.
Best Practices
- Respond quickly — Return a
200response within 5 seconds. Offload processing to a background queue or worker. - Always verify signatures — Check the
X-Celper-Signatureheader using HMAC-SHA256 before processing any event. This prevents spoofed deliveries. - Handle duplicates idempotently — Due to retries, your endpoint may receive the same event more than once. Use the
webhookIdandtimestampto deduplicate. - Monitor delivery health — Use the
GET /v1/webhooks/{id}/deliveriesendpoint to check for failed deliveries and diagnose issues. - Test before going live — Use the
POST /v1/webhooks/{id}/testendpoint to send a test event to your URL and verify your integration end-to-end.