Webhooks
PreviewReceive signed events as calls progress, check the signature, answer fast, and handle retries, duplicates and order safely.
RowCall sends a signed POST to your endpoint each time something happens on a run. Add endpoints in Developers → Webhooks, for live or test events, and choose which event types each one receives. URLs must be HTTPS. Runs started from the RowCall app announce events too, so your systems hear about every call.
The events
| Event | When |
|---|---|
run.queued | A run was made and is waiting to be placed |
run.ringing | An attempt is ringing |
run.answered | The shopper picked up |
run.no_answer | An attempt went unanswered (sent for every one, retry or not) |
run.completed | An answered call ended: the run carries its outcome and answers |
run.failed | The run gave up: no attempt reached the shopper, or it couldn't be placed |
run.canceled | The run was cancelled, or the shopper opted out before the next attempt |
action.completed | An action succeeded |
action.failed | An action was refused, timed out or isn't connected |
handoff.requested | A person was asked for on the call |
handoff.connected | A person took the call |
optout.received | The shopper asked not to be called again |
ping | Only from Send a test event, to check your endpoint |
Apps connected over OAuth also always get app.installed and app.uninstalled. See Build an integration.
What an event carries
Every event has the same envelope: its id, type, mode (live or test), the orgId it's about, createdAt, and data. Run events carry data.run as GET /v1/runs/{runId} shows it at that moment, including your metadata, so you rarely need to read the run again. Events about one call also carry data.callId, plus the action, the handoff or the opted-out phone. Each event's exact body and an example are in the reference.
Headers and signature
Each request carries three headers:
RowCall-Signature: t=1760348000,v1=5f2b…
RowCall-Event-Id: evt_c8v6b4n2m0l8k6j4h2g0
RowCall-Event-Type: run.completedv1 is an HMAC-SHA256, in hex, of t + . + the raw request body, keyed with your endpoint's signing secret (whsec_…, shown once when you add the endpoint or roll its secret). After you roll a secret, the old one keeps signing for 24 hours, so the header carries two v1 values; accept the request when any of them matches.
Check the signature before you parse the body, reject a t more than 5 minutes from now, and compare in constant time. The SDK does all three:
import { verifyWebhook } from '@rowcall/sdk'
import express from 'express'
const app = express()
// The raw body, not parsed JSON: the signature covers the exact bytes RowCall sent.
app.post('/rowcall/events', express.raw({ type: 'application/json' }), async (req, res) => {
const body = req.body.toString()
const isGenuine = verifyWebhook({
body,
signature: req.get('RowCall-Signature') ?? '',
secret: process.env.ROWCALL_SIGNING_SECRET,
})
if (!isGenuine) return res.sendStatus(401)
res.sendStatus(200)
await handleEvent(JSON.parse(body)) // after answering
})The reference has the same check in Python.
Answer fast, work later
Reply with any 2xx within 10 seconds, then do the work, on a queue if it can take a while. Anything else counts as a failure: another status, no reply in time, or a redirect (redirects are not followed).
Retries, duplicates and order
- Retries. A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, then marked failed. When a live endpoint gives up, your org's developers are told.
- Duplicates. Delivery is at least once: the same event can arrive more than once. Store the event
ids you've handled and skip repeats. See Idempotency. - Order. Events can arrive out of order, especially after a retry. Use
createdAtand the run's ownstatus, and read the run withGET /v1/runs/{runId}when order matters.
Delivery log and test events
Each endpoint in Developers → Webhooks has a delivery log: every event, how its last try went, and its body. You can send any delivery again, for example after fixing a bug in your handler. Send a test event sends a ping, so you can check your signature code before any run.
Let a call change things in your systems.