Webhooks
An outbound webhook is an HTTP POST that Ringtime sends to your system. It tells you what happened in a conversation, without you polling for it. Ringtime sends one POST per event to the single endpoint your organization registered.
Registering your endpoint
You cannot register a webhook endpoint with your API key. Registration is not part of the public API today.
Ask Ringtime to set it up. Send your HTTPS receiver URL to success@ringtime.ai. Ringtime
registers the URL and gives you a signing secret that starts with whsec_.
Ringtime shows the signing secret once, at registration. Store it in your own secret store. If you lose it, or you think it leaked, ask Ringtime to rotate it. A rotation issues a new secret and old signatures stop verifying.
The envelope
Every webhook body has the same four top-level fields.
Code
| Field | Type | What it is |
|---|---|---|
event | string | The event type. task.completed or interaction.finalized. |
schema_version | integer | The envelope version. 1 today. |
occurred_at | string | When the event happened, ISO 8601 in UTC with a Z suffix. Not when the POST was sent. |
data | object | The body for this event type. The shape depends on event. |
data is an open object. Ringtime adds fields over time and never removes one. Ignore
keys you do not know, and do not fail on them.
The two events
| Event | When | On by default |
|---|---|---|
task.completed | The task reached its final state. One per task. | Yes |
interaction.finalized | One contact attempt finished. One per attempt. | No |
interaction.finalized is opt-in. Ask Ringtime to add it to your endpoint if you want
per-attempt detail. A task with three attempts then sends three interaction.finalized
events and one task.completed.
task.completed
The business result of the whole task.
| Field | Type | What it is |
|---|---|---|
id | string | The task id, for example task_01HZX9Q2K9. |
campaign | object | The campaign that produced the task. See below. |
status | string | The final task status. completed today. |
outcome | string | The single headline result. See the values below. |
participant | object | The person Ringtime contacted. See below. |
ids | array | Your external ids, echoed from the trigger. Empty for a task you did not trigger. |
context | object | The context you sent on the trigger, returned as sent. Empty for a task you did not trigger. |
business_outcomes | object | The full picture of what the task achieved. See below. |
outcome is one of booked, qualified, not_eligible, screened, escalated,
callback, declined, opted_out, or none. none means Ringtime reached the person
but recorded no business result. The set can grow, so treat an unknown value as none
rather than failing. outcome is a summary. Read business_outcomes for the detail.
campaign carries external_id, the routing key you triggered with, and name, the
campaign name in the participant's language. Both are null when Ringtime has no
campaign for the task.
participant carries first_name, last_name, phone in E.164, and language, a
BCP 47 code or null.
Each entry in ids carries source, type and value, exactly as you sent them on the
trigger.
business_outcomes holds the outcomes Ringtime captured across every attempt of the task.
The keys are bookings and side_effects (arrays, one entry per occurrence) and
screening_assessment, lead_qualification, opt_out, disinterest, not_eligible,
escalation and callback_preference (an object or null, the most recent one wins).
Example
Code
interaction.finalized
One contact attempt, with its transcript. Where task.completed says what the task
achieved, this says whether Ringtime got through at all.
| Field | Type | What it is |
|---|---|---|
id | string | The interaction id, for example int_01HZX9Q2KA. |
task_id | string or null | The parent task id. null when the interaction has no task. |
campaign | object | Same shape as on task.completed. |
participant | object | Same shape as on task.completed. |
ids | array | Same shape as on task.completed. |
channel | string | voice, whatsapp, sms, chat or slack. |
direction | string | outbound or inbound. |
started_at | string or null | When the session started, if it did. |
ended_at | string or null | When the attempt ended, if known. |
duration_secs | integer or null | Session length in seconds, when both ends are known. |
reach | string or null | How far the attempt got. unreached, reached, engaged or resolved. Voicemail counts as unreached. |
answer_type | string or null | How the attempt was answered, for example answered_by_human, answered_by_voicemail, not_answered, not_answered_busy or replied. null when it does not apply. |
system_failure | boolean | true when Ringtime, not the person, failed the attempt. |
transcript | array | The conversation, turn by turn. |
business_outcome_events | array | The outcomes captured on this attempt. |
Each transcript turn carries role (agent or participant), text, and at, the time
of the turn. Voice turns are transcribed. The transcript carries speech only. Tool calls
and their results never appear in it, and there is no recording link.
Each entry in business_outcome_events carries id, kind, payload,
schema_version and occurred_at. kind names the shape of payload and can gain new
values, so skip an entry whose kind you do not know and keep processing the rest.
Example
Code
Verifying the signature
Verify every request before you trust it. Ringtime signs each POST with your secret, following the Standard Webhooks specification.
Three headers carry the proof.
| Header | What it is |
|---|---|
webhook-id | The event id. Stable across retries. Also your duplicate key. |
webhook-timestamp | When the request was signed, in Unix seconds. |
webhook-signature | v1, followed by the base64 signature. |
The v1, inside webhook-signature is the Standard Webhooks tag for the HMAC-SHA256
algorithm. It is a fixed part of the header format, not a version number, and it never
changes.
To verify:
- Take the key. Strip the
whsec_prefix from your secret and base64-decode the rest. Those raw bytes are the HMAC key. - Build the signed string. Join the
webhook-id, thewebhook-timestampand the raw request body with a dot:id.timestamp.body. Use the body exactly as received. Do not parse and re-serialize it. - Compute HMAC-SHA256 over that string with the key, and base64-encode the result.
- Compare
v1,plus your result against thewebhook-signatureheader, with a constant-time comparison. - Reject the request when
webhook-timestampis more than 300 seconds away from your own clock.
With a library
Use a Standard Webhooks library and you get all five steps for free. Pass the secret
exactly as Ringtime gave it to you, whsec_ prefix included.
Code
By hand
Code
Compare the output against the part of webhook-signature after v1,.
Delivery and retries
Delivery is at least once. Your endpoint can receive the same event twice, for example
when your 2xx answer is lost on the way back. Make your handler idempotent. Keep the
webhook-id of every event you processed and drop a repeat. A retry of the same event
carries the same webhook-id and the same body, byte for byte.
Answer fast. Ringtime waits 10 seconds for a response, then treats the attempt as failed. Acknowledge with a 2xx status first and do your own work afterwards.
Ringtime retries when your endpoint answers 429, answers 5xx, times out, or cannot be
reached. The wait grows exponentially from 1 minute, with full jitter, and is capped at
1 hour. On a 429 Ringtime honours a numeric Retry-After header, clamped to between
1 minute and 1 hour. A Retry-After given as a date is ignored.
Ringtime stops retrying in two cases:
- Your endpoint answers any other 4xx. That is a refusal, so Ringtime does not retry.
- 24 hours passed since the first attempt. The event is then dropped.
Requirements for your endpoint
- Use HTTPS. Plain HTTP is refused at registration.
- Be reachable from the public internet. Ringtime does not call private, loopback or link-local addresses.
- Do not put a username or password in the URL. Ringtime refuses such a URL.
- Answer within 10 seconds with a 2xx status.
- Be idempotent. Dedupe on the
webhook-idheader. - Verify the signature before you act on the body.

