Webhooks
Receive real-time events from Nerva when your agents complete calls, chats, or capture leads.
Webhooks let you receive real-time events from Nerva when your agents complete calls, chats, or capture leads. Instead of polling our API, configure a URL once and Nerva will POST events to it as they happen.
Setup
- Open Dashboard → Integrations.
- Click Webhook in the integrations catalog.
- Fill in:
- Name — any label you'll recognize ("Production Zapier", "QA endpoint").
- Webhook URL — the HTTPS endpoint that will receive events.
- Secret (optional but recommended) — used to sign requests with HMAC-SHA256. Click Generate for a strong random secret.
- Active — toggle off to pause delivery without deleting the config.
- Scope — see Scope below.
- Authentication — see Authentication below.
- Events — pick which events should fire this webhook. See the Event reference.
- Save. Click Send test event to verify your endpoint is reachable.
You can edit, pause, or delete a webhook from the same page at any time.
Scope
Webhooks fire for either every agent in your organization or a specific subset:
- All agents in this organization — fires on every event from every agent. Best for org-wide analytics or a single CRM endpoint.
- Selected agents only — fires only for agents you explicitly attach. Attach from each agent's Integrations tab. Best when one Nerva organization hosts multiple end-customers and each routes to its own CRM endpoint.
Both kinds of webhooks fire independently and additively — if you have an
org-wide webhook and a per-agent webhook that both subscribe to call.ended,
each call ending fires both webhooks (two HTTP POSTs, two delivery log rows).
Authentication
Two layers, both independent of each other:
HMAC signature (always-on when a secret is configured)
When you set a secret, every request carries an X-Nerva-Signature header
containing the hex SHA-256 HMAC of the raw request body. Verifying this
proves the request came from Nerva and the body wasn't tampered with in
transit. See Verifying webhook signatures for
code samples.
Customer-controlled Authorization header
Independent of HMAC, you can configure one of:
| Method | What we send |
|---|---|
| None | No Authorization header |
| Bearer token | Authorization: Bearer <your token> |
| Basic auth | Authorization: Basic <base64(user:pass)> |
| API key (custom header) | <your header name>: <your value> (e.g. X-API-Key) |
This is what most no-code platforms (Zapier, Make, n8n) and internal API gateways expect. You can use both HMAC and a customer auth header at the same time — the security guarantees stack.
Reserved header names you cannot override (Nerva owns these):
Content-Type, Content-Length, X-Nerva-Signature, X-Nerva-Timestamp.
Payload format
Every webhook is a POST with this envelope:
event— the event ID. See the full Event reference.timestamp— ISO 8601 UTC, set by Nerva at delivery time.organizationId— your Nerva organization ID.data— event-specific payload. All keys are camelCase.
Headers:
Content-Type: application/jsonX-Nerva-Signature: <hex>(when secret is configured)Authorization: ...(when configured)
Delivery semantics
- HTTP method:
POSTwith JSON body. - Timeout: 10 seconds per request. Endpoints that consistently take longer will be retried as failed.
- Retries: failed deliveries retry with exponential backoff up to 6
attempts spread over ~24 hours. We retry on
5xx,408,429, network errors, and DNS failures. - No retry on
4xx(except408and429). A4xxmeans the endpoint is misconfigured (auth, route, payload format) and retrying won't fix it. - Concurrency cap: at most 5 concurrent in-flight deliveries per webhook.
This prevents bursts of activity (e.g., a campaign ending 50 calls at once)
from overwhelming your endpoint with
429s. - Idempotency: each delivery has a stable identifier
(
integrationId:event:resourceId). If Nerva's internals re-emit the same event, your endpoint will receive exactly one request per delivery. - Ordering: not guaranteed across events.
call.endedandcall.analyzedfor the same call are emitted in order, but if*.endedretries while*.analyzedsucceeds, you may receive*.analyzedbefore the eventual*.endedretry. Build idempotent handlers keyed on the resource ID.
Successful response: any 2xx status code. Body is ignored.
Best practices
- Respond fast — return
2xximmediately, then process asynchronously. A 10s timeout looks like a failure to us; long-running processing inside the request will trigger retries. - Verify HMAC in production — always. See Verifying webhook signatures.
- Use HTTPS — webhooks contain transcripts and contact data. We do not restrict HTTP URLs at write time, but you should never use plain HTTP for real traffic.
- Use idempotency keys on your side too — events use
callId(orsessionIdfor messaging) as a stable resource identifier. If your handler is invoked twice for the samecallId + event, only act once. - Subscribe to what you actually need —
*.startedevents are off by default; turn them on only if you need them. Fewer subscriptions = fewer webhook deliveries to debug. - Pause, don't delete — toggle a webhook to Inactive when migrating endpoints. Inactive webhooks receive no events but stay in your list for re-activation, and any in-flight retries are dropped cleanly.
Debugging
- Test endpoint: in the webhook drawer, click Send test event to fire a synthetic payload at your URL with the production HMAC + auth headers.
- Recent deliveries: the webhook drawer shows the last 20 delivery attempts with status codes, retry counters, and inline error messages.
- Per-call activity: open any call detail page to see the webhook deliveries for that specific call alongside CRM activity.
See also
- Event reference — full event catalog with payload shapes and timing
- Verifying webhook signatures — HMAC verification examples in Node, Python, and Ruby