What RingCentral Webhooks Do
RingCentral webhooks push call, voicemail and telephony events to another system as they happen. For a CRM integration, that’s what lets calls log themselves, missed calls create follow-up tasks, and voicemails reach the right person without anyone exporting a call report.
Unlike most platforms, there’s no webhook settings screen in RingCentral. Webhooks are subscriptions created through the API, and they expire, renew and can be blocked if your endpoint misbehaves. That’s why they’re usually set up and managed by the integration rather than by hand. (RingCentral docs)
In This Post
Prerequisites
- A RingCentral developer account and an app (set one up first)
- App permissions: WebHook Subscriptions (
SubscriptionWebhook), plus the permission for each event you subscribe to: CallControl for telephony sessions, ReadMessages for voicemail - A public HTTPS endpoint to receive events (we provide this)
Which Events to Subscribe To
These are the event filters we use for call logging and follow-up. (Event filter index)
| Need | Event filter | Permission |
|---|---|---|
| Live call events (ringing, answered, ended) for the whole account | /restapi/v1.0/account/~/telephony/sessions | CallControl |
| Same, for one extension | /restapi/v1.0/account/~/extension/~/telephony/sessions | CallControl |
| Missed calls only | /restapi/v1.0/account/~/telephony/sessions?missedCall=true | CallControl |
| New voicemail | /restapi/v1.0/account/~/extension/~/voicemail | ReadMessages |
Two things people get wrong:
- There is no call-log event filter. The call log is something you query through the API after a call ends. For real-time events, use telephony sessions, then pull the finished call-log record for duration and result.
- The old
missed-callsfilter is legacy. Use telephony sessions withmissedCall=trueinstead.
Creating the Subscription
One API call creates the webhook:
curl -X POST "https://platform.ringcentral.com/restapi/v1.0/subscription" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eventFilters": [
"/restapi/v1.0/account/~/telephony/sessions",
"/restapi/v1.0/account/~/extension/~/voicemail"
],
"deliveryMode": {
"transportType": "WebHook",
"address": "https://hooks.example.com/ringcentral",
"verificationToken": "a-long-random-secret"
},
"expiresIn": 604800
}'
The validation handshake
The moment you create the subscription, RingCentral sends a test request to your address with a Validation-Token header. Your endpoint must:
- Echo the same value back in a
Validation-Tokenresponse header - Return 200 OK within 3 seconds
- Keep the whole response (headers and body) under 1,024 bytes
- Be reachable over HTTPS (TLS 1.2 or newer in production)
If any of that fails, the subscription isn’t created. You’ll see errors like SUB-520 (token not returned), SUB-521 (not reachable) or SUB-523 (HTTPS required). (Creating webhooks)
Verification token
The optional verificationToken is returned in a Verification-Token header on every delivery. Check it and reject anything that doesn’t match. It’s your proof the request came from your subscription. (Receiving webhooks)
What a Call Event Looks Like
Every notification has the same envelope: uuid, event, timestamp, subscriptionId, ownerId and a body. Here’s a trimmed telephony session event for an inbound call that has just started ringing:
{
"uuid": "1823485288313944239",
"event": "/restapi/v1.0/account/809646016/telephony/sessions",
"timestamp": "2026-10-01T15:27:18.390Z",
"subscriptionId": "53300e7c-…",
"ownerId": "809646016",
"body": {
"sequence": 3,
"telephonySessionId": "s-a6abd…",
"sessionId": "425008160016",
"eventTime": "2026-10-01T15:27:18.340Z",
"origin": { "type": "Call" },
"parties": [
{
"id": "p-a6abd…-2",
"extensionId": "809646016",
"direction": "Inbound",
"from": { "phoneNumber": "+17135550142" },
"to": { "phoneNumber": "+17139369330", "extensionId": "809646016" },
"status": { "code": "Setup", "rcc": false },
"missedCall": false
}
]
}
}
(Telephony session notifications)
A single call produces a series of these events with the same telephonySessionId. The party’s status.code moves through values like Setup, Proceeding, Answered, Hold, VoiceMail and Disconnected. To log a call in a CRM you typically:
- Match
from.phoneNumberto a contact when the call starts (Setup/Proceeding) - Note whether it was answered (
Answered) or went to voicemail - On
Disconnected, treat the call as finished. IfmissedCallis true, create the follow-up task - Fetch the call-log record for the final duration and result, and the recording if you need it (call recording access)
Use sequence to order events. They can arrive out of order.
Renewal: The Part That Breaks Integrations
Subscriptions expire. You can request up to 10 years (expiresIn: 315360000), but if you leave it out, RingCentral’s own example returns about 7 days. Either way, the integration should track expirationTime and renew before it lapses:
curl -X POST "https://platform.ringcentral.com/restapi/v1.0/subscription/SUBSCRIPTION_ID/renew" \
-H "Authorization: Bearer ACCESS_TOKEN"
You can also subscribe to /restapi/v1.0/subscription/~?threshold=…&interval=… to get a reminder event before expiry.
Blacklisted subscriptions
A subscription has two documented statuses: Active and Blacklisted. If your endpoint stops returning 200 (errors, timeouts, an outage), RingCentral can blacklist the subscription and stop delivering. You’ll see blacklistedData with a reason such as “Read timed out”.
Blacklisting is permanent. You can’t renew your way out. Delete the subscription and create a new one (the same URL is fine). (Troubleshooting)
That’s the strongest argument for the receiving pattern below.
How the Receiving Endpoint Should Behave
@app.post("/ringcentral")
def ringcentral_webhook(request):
token = request.headers.get("Validation-Token")
if token: # subscription handshake
return Response(status=200, headers={"Validation-Token": token})
if request.headers.get("Verification-Token") != SECRET:
return Response(status=401)
queue.enqueue(process_rc_event, request.json()) # do the CRM work in the background
return Response(status=200) # always answer fast, with a small response
- Answer every delivery with a fast, small 200. Slow responses and errors lead to blacklisting.
- Process in the background. Contact lookups and CRM writes belong in a worker, not in the request.
- De-duplicate. Use the
uuid(andtelephonySessionId+sequence) so a repeated delivery doesn’t log a call twice. - Backfill after gaps. If the subscription was down, query the call log for the missed window when you recreate it.
Testing
RingCentral discontinued its sandbox environment on January 1, 2025, so testing now happens against a production account, ideally a separate developer account or test extensions. (RingCentral community announcement)
- Run the endpoint locally behind a tunnel (ngrok or similar), as in RingCentral’s own quick start, and create a subscription pointing at it.
- Make real test calls: answered, missed, sent to voicemail, transferred. Check each produces the event sequence you expect.
- Use the API Explorer to create, read and delete subscriptions without writing code.
- Read the subscription back (
GET /restapi/v1.0/subscription/{id}) to confirmstatus: Activeand the expiration time.
Troubleshooting
Events Stopped Arriving
Read the subscription. If it’s expired, renew or recreate it. If it’s Blacklisted, fix the endpoint (look at the reason), delete the subscription and create a new one, then backfill from the call log.
Subscription Creation Fails
Usually the validation handshake: the token isn’t echoed in a header, the response is slower than 3 seconds or larger than 1,024 bytes, or the URL isn’t public HTTPS. Firewalls and VPN-only endpoints block it.
“Too Many Subscriptions” (SUB-505)
Each user or extension can have up to 20 subscriptions per app. Integrations that create a new subscription on every restart without deleting the old one hit this. List and clean up existing subscriptions first.
Duplicate Calls in the CRM
A call produces several events, and deliveries can repeat. De-duplicate on the telephony session and only create the CRM record once per call.
Missing Permission Errors
Each event filter needs its own app permission: CallControl for telephony sessions, ReadMessages for voicemail. Add it in the developer console and re-authorize.
Next Steps
- Set up recording access for logged calls: RingCentral Call Recording
- Create the app first: RingCentral Developer Account
- General webhook debugging: Common Webhook Issues
- Rather we handle it? VoIP + CRM Integration