RingCentral
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:
- Reads the RingCentral telephony session party data.
- Finds the assigned Tempr user by RingCentral extension ID or extension number.
- Finds a customer by the remote phone number.
- Stores or updates a
callsrow. - Broadcasts
telephony-call.updatedon the authenticated user's private Reverb channel. - 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:
- Open
/telephony. - Go to
Extensions. - Find the Tempr user.
- Choose
Assign Extension. - Select the RingCentral extension from the searchable dropdown.
- 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:
AnsweredHoldParked
Active outbound statuses:
SetupProceedingAnsweredHoldParked
Terminal statuses:
DisconnectedGone
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.