Ringhum.

Developers

Webhooks

Receive signed HTTPS notifications when calls end, messages are taken, appointments, orders and reservations change, or a campaign finishes, and verify that each one really came from Ringhum.

6 min read Updated 24 September 2026

On this page

A webhook sends an HTTPS request to your server the moment something happens in your workspace. Use webhooks instead of polling the API: when a call ends, you receive the summary, outcome, collected details and transcript straight away. Each delivery is signed with a secret that only you and Ringhum know, so your server can reject anything that did not come from Ringhum.

Add an endpoint

Only the workspace Owner and Admins can add, test and remove endpoints.

  1. Open Developers in the sidebar.
  2. In the Webhooks card, click Add endpoint.
  3. Enter the Endpoint URL. It must start with https://.
  4. Under Events, tick the events you want. call.ended is ticked for you.
  5. Click Add endpoint.
  6. Copy the signing secret from the green box (it starts with whsec_) and store it on your server. Click Done.

Important: The signing secret is shown only once. If you lose it, remove the endpoint and add it again to get a new secret.

You can add as many endpoints as you need. Webhooks cover every assistant in the workspace. You can also manage endpoints through the API, with POST /webhooks and a key that has the write scope.

Events

Event When it is sent
call.started A call was answered by an assistant
call.ended A call ended and was summarised (includes transcript and summary)
call.transferred A call was transferred to a human
message.taken The assistant took a message
appointment.booked The assistant booked an appointment
appointment.rescheduled An appointment was moved to a new time
appointment.cancelled An appointment was cancelled
order.created An order was placed (by an assistant, by hand or through the API)
order.updated An order moved to a new status
order.cancelled An order was cancelled
reservation.created A room was reserved (by an assistant, by hand or through the API)
reservation.updated A reservation moved to a new status (confirmed, checked in, checked out, no-show)
reservation.cancelled A reservation was cancelled
campaign.completed An outbound campaign finished calling its list
test.ping You clicked Send test

What a delivery looks like

Every delivery is a POST with a JSON body and these headers:

POST https://example.com/hooks/ringhum
Content-Type: application/json
User-Agent: Ringhum-Webhooks/1.0
X-Ringhum-Event: call.ended
X-Ringhum-Timestamp: 1789250000
X-Ringhum-Signature: v1=5f1a…

{
  "id": "evt_8f2k…",
  "type": "call.ended",
  "created_at": "2026-09-24T14:03:11+00:00",
  "data": {
    "call_id": 4812,
    "direction": "inbound",
    "from": "+14155550132",
    "to": "+14155550100",
    "assistant_id": 3,
    "status": "completed",
    "outcome": "message_taken",
    "sentiment": "neutral",
    "duration_seconds": 94,
    "summary": "Caller asked for a quote for a kitchen repair…",
    "extracted": { "name": "Dana Lee", "reason": "Quote" },
    "transcript": [
      { "role": "assistant", "content": "Hello, you've reached…" },
      { "role": "user", "content": "Hi, I'd like a quote…" }
    ]
  }
}

The data object depends on the event:

  • call.ended and message.taken: the call fields above.
  • call.started and call.transferred: call_id, direction, from, to, assistant_id, status and started_at.
  • appointment.*: appointment_id, title, status, starts_at, ends_at, timezone, customer (name, phone, email), contact_id, service, staff, call_id and notes.
  • order.*: order_id, number, status, fulfillment, customer, address, items with options and prices, currency, subtotal, fee, total, source, call_id and, for cancellations, cancel_reason.
  • reservation.*: reservation_id, number, status, room, check_in, check_out, nights, adults, children, guest, currency, nightly, total and source.
  • campaign.completed: campaign_id, name and the campaign's progress counts.

Note: When the assistant books an appointment during a call, you can receive appointment.booked twice. One is sent as the booking is made, with the appointment fields. The other is sent after the call is summarised, with the call fields. Check whether data has appointment_id or call_id and use the id field to avoid processing a delivery twice.

Verify the signature

The signature is an HMAC-SHA256 of the timestamp, a dot, and the raw request body, keyed with your endpoint's signing secret. It is sent as v1= followed by the hex digest.

  1. Read the raw body exactly as received, before any JSON parsing.
  2. Build the string {X-Ringhum-Timestamp}.{raw body}.
  3. Compute the HMAC-SHA256 with your secret and prefix it with v1=.
  4. Compare it with X-Ringhum-Signature using a constant-time comparison.
  5. Reject the request if the timestamp is more than five minutes old, to block replayed requests.

Node.js:

const crypto = require('crypto');

function isValid(req, rawBody, secret) {
  const ts = req.header('X-Ringhum-Timestamp');
  const sig = req.header('X-Ringhum-Signature') || '';
  const expected = 'v1=' + crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

PHP:

$ts = $_SERVER['HTTP_X_RINGHUM_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_RINGHUM_SIGNATURE'] ?? '';
$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . file_get_contents('php://input'), $secret);
if (! hash_equals($expected, $sig) || abs(time() - (int) $ts) > 300) {
    http_response_code(400);
    exit;
}

Responses, timeouts and retries

  • Reply with any 2xx status to confirm the delivery. Do slow work after you respond.
  • Ringhum waits up to 10 seconds for your response.
  • If your endpoint does not answer with a 2xx, the delivery is tried up to 3 times in total: at once, again after about 10 seconds, and a last time about 60 seconds later. After that it is dropped.
  • Deliveries can arrive out of order. Use created_at and the ids in data to put them in order.

The Webhooks table shows each endpoint's last HTTP status and when it was sent. A failed endpoint stays active; Ringhum does not switch it off for you.

Test, pause and remove

  • Send test sends a test.ping event straight away, and a message shows the HTTP status your endpoint returned.
  • The Active switch pauses an endpoint. Events that happen while it is off are not sent later.
  • Remove deletes the endpoint and its secret.

Common questions

What is the difference from the Zapier, Make and n8n apps under Integrations? Those connections use the Integrations page and its six event types. Their body is {"event", "workspace", "sent_at", "data"}, and when you set a secret the signature header is X-Ringhum-Signature: sha256=…, an HMAC-SHA256 of the body alone. Developer webhooks, described here, cover more events and use the v1= signature with a timestamp. See Choosing what gets sent.

My endpoint returns 2xx but my code sees nothing. Check that the event is ticked for that endpoint, that the endpoint is active, and that your firewall allows requests from the internet.

Why does signature checking fail? Most often the body was parsed and re-encoded before hashing. Hash the raw bytes exactly as received.

Still stuck?

Email hello@ringhum.com or send us a message. Team and Scale plans get priority support.

Contact support