Webhooks
Endpoints, events, deliveries debugger, secret rotation.
What webhooks do
A webhook is an HTTP request that the platform sends to your server when something happens. Examples:
- A new tenant signs up → we POST a JSON payload to your endpoint with the tenant's details
- A call completes → we POST the call record to your endpoint so you can sync it to your CRM
- A payment fails → we POST so you can trigger a follow-up email from your own system
Webhooks are how you build real-time integrations without polling our API.
Where webhook setup lives
Top nav → Settings → Webhooks. Click Add Endpoint.
The form:
- Endpoint URL — your server's URL, must be
https. Examples:https://api.yourcompany.com/webhooks/simuwave,https://hooks.zapier.com/hooks/catch/12345/abcdef - Description — optional, for your own reference
- Events to send — checkboxes grouped by category. Pick exactly the events your integration cares about. Select All during testing if you want to see everything; tighten down later.
Click Create Endpoint.
The signing secret is shown exactly once. Copy it immediately. You'll use it server-side to verify that webhook requests genuinely came from us.
Available events
| Category | Events |
|---|---|
| Tenants | tenant.created, tenant.updated, tenant.suspended, tenant.unsuspended, tenant.deleted |
| Phone Numbers | phone_number.purchased, phone_number.assigned, phone_number.released, phone_number.updated |
| Extensions | extension.created, extension.updated, extension.deleted |
| Billing | subscription.updated, subscription.activated, invoice.created, invoice.paid, invoice.failed, payment_method.attached, payment.failed, checkout.session.completed |
| Calls | call.initiated, call.answered, call.completed, call.failed |
| Inbound Calls | inbound_call.ringing, inbound_call.answered, inbound_call.completed, inbound_call.abandoned |
Pick only the events you'll actually use. Subscribing to everything wastes your server's time processing payloads you'll just discard.
Payload shape
Every webhook is a POST request with Content-Type: application/json. The body looks like:
{
"id": "evt_abc123",
"type": "tenant.created",
"created_at": "2026-05-04T14:23:45Z",
"partner_id": "ptr_xyz789",
"data": {
"tenant": {
"id": "tnt_def456",
"name": "ACME Plumbing",
"sip_prefix": "acme",
"status": "TRIAL",
...
}
}
}
The data field's shape is event-specific. Full schema for every event is at api.simuwave.com under "Webhook Events."
Verifying signatures
Every webhook request includes a X-Simuwave-Signature header containing an HMAC-SHA256 of the raw request body, signed with your endpoint's secret.
Pseudocode for verification:
expected = hmac_sha256(secret, raw_body).hex()
received = request.headers['X-Simuwave-Signature']
if not constant_time_equal(expected, received):
return 401 # not from us
Always verify. Without verification, anyone who knows your endpoint URL can POST fake events to it.
Delivery and retries
We attempt delivery up to 8 times over ~24 hours with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 4 hours |
| 7 | 12 hours |
| 8 | 24 hours |
We consider delivery successful if your endpoint returns any 2xx status code within 10 seconds. Anything else (timeout, 4xx, 5xx) counts as a failed attempt.
After 8 failed attempts, we stop. The event is marked DEAD.
If your endpoint is consistently failing, your endpoint Status will show Consecutive Failures: N. If N reaches 100, we automatically disable the endpoint to stop wasting resources. You can re-enable it once you've fixed your server.
The deliveries debugger
Each endpoint row in the Webhooks list has a Deliveries action. Click it.
A modal opens showing the last 100 delivery attempts:
- Event Type
- Status (DELIVERED / PENDING / FAILED / DEAD)
- Attempts (e.g., 3/8)
- Last Attempt timestamp
- Response Code (200, 500, "Timeout", etc.)
- Redeliver button
Use this when integrating to confirm your endpoint is receiving things correctly. Each delivery card expands to show the full request body and the response your server returned.
Redeliver is essential for debugging. Trigger an event in our dashboard, see it land in your Deliveries view, and click Redeliver as many times as you need while you debug your endpoint code. It's the same payload every time.
Rotating secrets
If you suspect your endpoint secret was leaked (e.g., a developer pushed it to a public git repo), click Rotate Secret on the endpoint row. A new secret is generated and shown to you exactly once.
The old secret stops working immediately. Update your endpoint code to use the new secret. There's no grace period — plan the rotation during a maintenance window.
Disabling and deleting
- Toggle Status — disable the endpoint without deleting it. Useful for temporary maintenance.
- Delete — remove the endpoint entirely. Past delivery history is preserved but no new events will be sent.
Common patterns
Webhook → Slack notification
A 5-minute Zapier integration. Subscribe to tenant.created, point the URL at a Zapier "Catch Hook" trigger, action = "Send Slack message." Now your sales team gets a Slack notification when a new tenant signs up.
Webhook → CRM contact creation
POST events to your CRM's API endpoint. Map tenant.created → create a HubSpot/Salesforce account.
Webhook → call analytics ingestion
Subscribe to call.completed and inbound_call.completed. POST to your data warehouse's ingestion endpoint. Now you have real-time call data flowing into BigQuery / Snowflake / Postgres.
Webhook → invoice processing
Subscribe to invoice.paid. POST to your accounting system to mark the corresponding entry paid.
Common pitfalls
- No signature verification — accept the consequences (someone WILL eventually find your endpoint URL)
- Slow endpoints — if you do heavy work synchronously, return 200 immediately and process asynchronously (queue + worker pattern). Webhooks that exceed 10s timeout count as failed.
- Returning 4xx for valid requests — we'll retry several times then stop. If you accidentally throw a 400 for a valid event, the event is lost.
- Subscribing to too many events — every event is processing time on your server. Subscribe to what you need, not everything.
- Forgetting to handle duplicates — under rare conditions (network blips during the response), we may send the same event twice. Use the
idfield for idempotency.