RingCentral

Reverb CLI ENV

The RingCentral integration listens for telephony session webhooks, stores the active call state, matches the caller or recipient phone number to a customer, and broadcasts call context into the app header through Reverb.

This page is for Tempr admins who manage RingCentral setup and day-to-day phone activity inside Tempr.

What The Integration Does

RingCentral sends webhook events to:

POST /integrations/ringcentral/webhooks/callback

When a webhook is accepted, Tempr:

  1. Reads the RingCentral telephony session party data.
  2. Finds the assigned Tempr user by RingCentral extension ID or extension number.
  3. Finds a customer by the remote phone number.
  4. Stores or updates a calls row.
  5. Broadcasts telephony-call.updated on the authenticated user's private Reverb channel.
  6. Updates the phone helper in the application header.

Outbound calls are also supported. RingCentral outbound Setup and Proceeding states are treated as active call context, so the header can update while the call is dialing.

The RingCentral webhook intergration does not utilize the Tempr EventBus due to vendor webhook validations. Webhooks are directed straight to the instance's /integrations/ringcentral/webhooks/callback route, which handles RingCentral's unique validation flow and accepts unauthenticated requests from RingCentral. The callback controller then processes the event and broadcasts to Reverb without requiring an authenticated user context or EventBus state.

Required Environment Variables

Add these values to .env.

BROADCAST_DRIVER=reverb

RINGCENTRAL_SERVER=https://platform.ringcentral.com
RINGCENTRAL_CLIENT_ID=
RINGCENTRAL_CLIENT_SECRET=
RINGCENTRAL_JWT_TOKEN=
RINGCENTRAL_WEBHOOK_URL=https://your-public-host.example.com/integrations/ringcentral/webhooks/callback
RINGCENTRAL_WEBHOOK_VALIDATION_TOKEN=
RINGCENTRAL_WEBHOOK_RENEWAL_THRESHOLD=86400
RINGCENTRAL_WEBHOOK_EVENT_FILTERS=/restapi/v1.0/account/~/telephony/sessions

REVERB_APP_ID=
REVERB_APP_KEY=
REVERB_APP_SECRET=
REVERB_HOST=tempr.test
REVERB_PORT=8080
REVERB_SCHEME=https
REVERB_BROADCAST_HOST=127.0.0.1
REVERB_BROADCAST_VERIFY_SSL=false

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Notes On Env Values

RINGCENTRAL_WEBHOOK_URL must be publicly reachable by RingCentral. For local testing, use the active ngrok or public tunnel URL.

RINGCENTRAL_WEBHOOK_VALIDATION_TOKEN is an internal token Tempr sends to RingCentral when creating the subscription. RingCentral may include it on event callbacks. Tempr rejects explicitly wrong tokens, but accepts real event callbacks when RingCentral omits the header.

REVERB_HOST is the browser websocket host. In local Herd/Valet usage, this is normally tempr.test.

REVERB_BROADCAST_HOST is the host Laravel uses when publishing events to Reverb. Use 127.0.0.1 locally if PHP cannot resolve tempr.test.

When changing .env, clear cached config:

php artisan optimize:clear

If Vite is running, restart it after changing any VITE_ variables.

CLI Commands

Create Or Renew The Webhook Subscription

Run:

php artisan ringcentral:webhook:sync

This command:

  • Authenticates to RingCentral with the configured JWT credentials.
  • Creates the webhook subscription if one does not exist.
  • Renews the subscription when it is close to expiration.
  • Recreates the subscription if RingCentral no longer has the saved subscription.

The scheduler runs the same command every 30 minutes:

$schedule->command('ringcentral:webhook:sync')->everyThirtyMinutes()->withoutOverlapping();

Force Recreate The Subscription

If the webhook URL changed or RingCentral has stale subscription state, delete and recreate it:

php artisan ringcentral:webhook:sync --delete
php artisan ringcentral:webhook:sync

Verify The Callback Route Exists

php artisan route:list --path=ringcentral

Expected route:

POST integrations/ringcentral/webhooks/callback

Verify Reverb Is Reachable From Laravel

Local TLS Reverb should answer:

php -r 'require "vendor/autoload.php"; $app = require "bootstrap/app.php"; $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); $response = Illuminate\Support\Facades\Http::withOptions(["verify" => false])->get("https://127.0.0.1:8080/up"); var_export([$response->status(), $response->body()]);'

Expected:

array (
  0 => 200,
  1 => '{"health":"OK"}',
)

Run Focused Tests

php artisan test tests/Feature/RingCentralSubscriptionManagerTest.php tests/Feature/RingCentralExtensionDirectoryTest.php tests/Feature/RingCentralWebhookTest.php

Telephony Panel User Setup

RingCentral webhooks do not reliably send human extension numbers. Tempr must know which RingCentral extension belongs to each app user.

Set this up in the /telephony Filament panel:

  1. Open /telephony.
  2. Go to Extensions.
  3. Find the Tempr user.
  4. Choose Assign Extension.
  5. Select the RingCentral extension from the searchable dropdown.
  6. Save the action.

The dropdown is populated live from RingCentral:

GET /restapi/v1.0/account/~/extension?type=User&status=Enabled

Extensions already assigned to other Tempr users are hidden.

The assignment stores these values on the user:

  • users.extension: RingCentral extension number, kept for display/backward compatibility.
  • users.meta.ringcentral_extension_id: RingCentral's internal extension ID.
  • users.meta.ringcentral_extension_number: RingCentral extension number.
  • users.meta.ringcentral_extension_name: RingCentral display name.
  • users.meta.ringcentral_extension_email: RingCentral extension email.

To remove an assignment, use the same row actions menu and choose Clear Extension.

Users cannot change their own RingCentral extension from the header. The header phone helper is display-only.

Call Logs And Recordings

Open /telephony and go to Calls to:

  • review recent calls
  • search by user, customer, caller, or phone number
  • filter by direction, status, or active state
  • open call details
  • open a recording link when one is available

If a call does not show a recording action, Tempr does not currently have a recording URL stored for that call.

Runtime Flow

Subscription Validation

When RingCentral validates the webhook URL, it sends a Validation-Token header without a normal event payload. Tempr must echo the same header back with a 200 response.

Event Callback

For normal telephony events, Tempr processes body.parties.

Active inbound statuses:

  • Answered
  • Hold
  • Parked

Active outbound statuses:

  • Setup
  • Proceeding
  • Answered
  • Hold
  • Parked

Terminal statuses:

  • Disconnected
  • Gone

When a terminal status arrives, Tempr marks the matching active call inactive and broadcasts that state so the header clears.

Troubleshooting

Webhook Returns 403

Check RINGCENTRAL_WEBHOOK_VALIDATION_TOKEN.

Tempr rejects callbacks when RingCentral sends a Validation-Token header that does not match .env. Tempr accepts callbacks with no token header.

After changing the token:

php artisan optimize:clear
php artisan ringcentral:webhook:sync --delete
php artisan ringcentral:webhook:sync

Webhook Returns 200 But Header Does Not Update

Check whether a call was processed. A 200 response with processed: 0 means the webhook was accepted but no party matched a Tempr user.

Common causes:

  • The user does not have a RingCentral extension assigned in /telephony.
  • The assigned RingCentral extension ID does not match the payload.
  • The payload status is not one of the active or terminal statuses listed above.
  • Reverb is connected in the browser but Laravel cannot publish to Reverb.

Browser Connects To Reverb But No Events Arrive

Verify Laravel can reach Reverb:

php -r 'require "vendor/autoload.php"; $app = require "bootstrap/app.php"; $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); $response = Illuminate\Support\Facades\Http::withOptions(["verify" => false])->get("https://127.0.0.1:8080/up"); var_export([$response->status(), $response->body()]);'

If PHP cannot resolve tempr.test, keep:

REVERB_BROADCAST_HOST=127.0.0.1
REVERB_BROADCAST_VERIFY_SSL=false

WebSocket Uses The Wrong Host Or Protocol

The browser bundle uses the VITE_REVERB_* variables. Restart Vite and rebuild assets after changes:

npm run build

Then hard refresh the browser.

In the browser console, this should show the connection state:

window.Echo?.connector?.pusher?.connection?.state

Expected values while healthy are usually connecting or connected.