# RingCentral Webhooks

## When to Use This Skill

* How do I receive RingCentral webhooks?
* How do I complete the RingCentral Validation-Token handshake?
* How do I verify RingCentral notifications with a Verification-Token?
* How do I create a RingCentral subscription (`POST /restapi/v1.0/subscription`)?
* Why is my RingCentral subscription getting blacklisted?
* How do I handle `message-store`, `presence`, or `telephony/sessions` events?

## Verification (core)

RingCentral does not HMAC-sign webhooks and does not follow the Standard
Webhooks spec. Authenticity relies on two mechanisms:

1. Validation-Token handshake (mandatory). When a subscription is created or
  renewed, RingCentral sends a request carrying a `Validation-Token` request
  header. Your handler must echo that exact value back in a `Validation-Token`
  response header and return `200` — fast (within a few seconds). No body is
  required.
2. Verification-Token (optional). Set an arbitrary `verificationToken` string
  on the subscription. RingCentral then sends it as a `Verification-Token` header
  on every notification. Compare it (timing-safe) to reject spoofed requests.

Node:

```javascript
const crypto = require('crypto');

// Timing-safe string compare for the Verification-Token header.
function tokenMatches(received, expected) {
  const a = Buffer.from(received || '', 'utf8');
  const b = Buffer.from(expected || '', 'utf8');
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

// In your POST handler:
const validationToken = req.get('Validation-Token');
if (validationToken) {                          // 1. handshake — echo + 200
  res.set('Validation-Token', validationToken);
  return res.status(200).json({ status: 'ok' });
}
if (EXPECTED_TOKEN && !tokenMatches(req.get('Verification-Token'), EXPECTED_TOKEN)) {
  return res.status(401).json({ error: 'Invalid verification token' }); // 2. auth
}

```

Python:

```python
import hmac

# 1. handshake — echo the Validation-Token back and return 200:
#    if validation_token: return Response(headers={"Validation-Token": validation_token})
# 2. optional per-notification auth:
def token_matches(received: str, expected: str) -> bool:
    return hmac.compare_digest(received or "", expected or "")

```

> For complete handlers with route wiring, event dispatch, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ringcentral-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ringcentral-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ringcentral-webhooks/examples/fastapi/)

## Common Event Types

RingCentral events are identified by the event filter (an API resource path)
in the notification's `event` field, not by a short name. Common filters:

| Event filter | Triggered When |
| --- | --- |
| `/restapi/v1.0/account/~/extension/~/message-store` | New message (SMS, voicemail, fax) |
| `/restapi/v1.0/account/~/extension/~/message-store/instant?type=SMS` | Inbound SMS (instant) |
| `/restapi/v1.0/account/~/extension/~/presence` | Extension presence changes |
| `/restapi/v1.0/account/~/telephony/sessions` | Call (telephony session) lifecycle |
| `/restapi/v1.0/account/~/extension/~/telephony/sessions` | Per-extension call events |
| `/restapi/v1.0/account/~/extension` | Extension created/updated/deleted |

> For the full event filter reference, see [RingCentral Event Types](https://developers.ringcentral.com/guide/notifications/manual/event-filters).

## Important Headers

| Header | Direction | Description |
| --- | --- | --- |
| `Validation-Token` | request → response | Handshake token to echo back on subscribe/renew |
| `Verification-Token` | request | Your configured token, sent on every notification |

## Environment Variables

```bash
# Optional shared secret; set as `verificationToken` when creating the subscription.
RINGCENTRAL_VERIFICATION_TOKEN=your_verification_token

```

## Local Development

```bash
# Start tunnel (no account needed). Address must be HTTPS — the tunnel provides it.
npx hookdeck-cli listen 3000 ringcentral --path /webhooks/ringcentral

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ringcentral-webhooks/references/overview.md) - RingCentral webhook concepts and events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ringcentral-webhooks/references/setup.md) - Creating and renewing subscriptions
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ringcentral-webhooks/references/verification.md) - Handshake and Verification-Token details