Skip to content

RingCentral Integration

This integration pulls RingCentral REST API v1.0 data into Resplendent Data for call reporting and CS dashboards.

The integration never creates, updates, or deletes records in RingCentral. It only POSTs to the token endpoint. Sending SMS, placing calls, downloading recording audio, live presence webhooks, and Analytics as a separate product are out of this connector.

You need credentials from your RingCentral Developer Console. Resplendent Data does not register a vendor app for you.

  1. A private REST API App with JWT authentication
  2. The app Client ID and Client Secret
  3. A JWT credential for a Super Admin or dedicated service user in the same RingCentral org
  4. The API environment: Production (https://platform.ringcentral.com) or Sandbox (https://platform.devtest.ringcentral.com). Default is Production.

Treat Client ID, Client Secret, and JWT like passwords. Share them through Nordpass or a similar vault, not email.

Suggested read-only app scopes: ReadCallLog, ReadAccounts / ReadAccountInfo, ReadMessages, and ReadPresence for User Presence and Queue Member Presence. Queue discovery and Queue Members require ReadAccounts. Do not grant send-SMS, call-control, or write scopes.

Company call log requires the JWT user to have Super Admin (or Read Account call log). Extension mailboxes and account info still need the matching read scopes.

User Presence also requires the JWT user to have ReadPresenceStatus and access to the employees’ presence. Existing apps must add ReadPresence before syncing this Dataset; Save & Test checks account info and call logs, not presence access.

Dataset Sync behavior Notes
Call Logs Incremental; first batch then page history Company call log GET /restapi/v1.0/account/~/call-log. Watermark is startTime. Nightly resync recovers deletes
Extensions Snapshot every 1440 minutes by default GET /restapi/v1.0/account/~/extension. Full list in one job
Messages Incremental; first batch then mailbox crawl Per-extension message-store (no account-level list). Watermark is creationTime. Needed for SMS/text reporting
User Presence Snapshot every 1 minute by default GET /restapi/v1.0/account/~/presence. Current phone and availability state per extension
Queue Members Snapshot every 1440 minutes by default; minimum 360 minutes Lists queues, then all members of each queue
Queue Member Presence Snapshot every 1 minute by default; minimum 1 minute Lists queues, then reads each member’s queue acceptance flags

Call Logs are incremental with integer page / perPage paging (ConnectWise style). initial_pull writes one configured batch (batch_pull_size, default 250). Call Logs use view=Detailed so lastModifiedTime is present; RingCentral caps Detailed perPage at 250, and that cap is the Dataset batch size so crawler_step stays one vendor page. Then get_old_rows continues from crawler_step. last_resync_timestamp does not change fetch size. History uses dateFrom=2010-01-01T00:00:00.000Z so RingCentral does not default to the last 24 hours.

Messages are incremental with a JSON mailbox cursor in crawler_step_info (extension_id + page). There is no account-wide message-store; the job lists User, Department, and Voicemail extensions, then pages each mailbox. Incremental updates use dateFrom from the warehouse watermark plus a two-minute overlap.

Extensions are snapshots. initial_pull returns the current list. Later scheduled ticks replace parquet. get_old_rows is unused.

User Presence is also a snapshot, with a default and minimum sync interval of 1 minute. Each sync reads all presence pages and replaces the previous snapshot. Removed extensions disappear on the next successful sync. It does not retain status history or capture calls that begin and end between polls. The interval is a scheduling setting, not a maximum age guarantee: dispatcher timing, queue processing, API throttling, and dashboard refresh can add delay. Display observed_at alongside status so viewers can see when the data was last observed.

Queue Members and Queue Member Presence are complete snapshots. Each pull discovers every queue using GET /restapi/v1.0/account/~/call-queues, then reads /call-queues/{queueId}/members or /call-queues/{queueId}/presence. All pages are included. Queue names and numbers are included in each row, so a separate Call Queues Dataset is not required. Queue Member Presence discovers the current queue list on every poll, independently of the slower Queue Members refresh.

Queue polling needs at least one list request plus one request per queue, with additional requests for pagination. Large accounts or shared API traffic may require a slower presence interval. Both Datasets use the existing 60-second worker timeout. Failed or incomplete scheduled pulls leave the previous snapshot in place; no partial membership list is published. Successful pulls remove memberships or queues that disappeared. An empty queue produces no membership rows.

Hard deletes on incremental Datasets are recovered by nightly resync (wipe, then the first configured batch, then history recrawl). Snapshot replace already drops extensions that left RingCentral. Call recording metadata is stored as scalars plus recording_json; audio files are not downloaded.

  1. Sign in to the RingCentral Developer Console
  2. Open Apps and create a REST API App
  3. Choose JWT authentication, private app, and the read-only scopes above
  4. Copy the Client ID and Client Secret
  1. Open the name menu → Credentials
  2. Create a JWT for a Super Admin or dedicated service user in the same org
  3. Restrict the JWT to the app you just created
  4. Copy the JWT once; it cannot be shown again
  1. Go to Settings → Integrations
  2. Find RingCentral and click Connect
  3. Choose Production or Sandbox
  4. Paste the Client ID
  5. Paste the Client Secret and JWT credential
  6. Click Save & Test
  7. After a successful test, click Sync Dataset and choose the Datasets to sync

Save & Test exchanges the JWT for an access token (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer), reads account info, then probes one company call-log row so a missing Super Admin permission fails immediately.

  • Auth is the official JWT grant. Access tokens are reused until near expiry, refreshed with refresh_token when valid, and only re-exchanged with the JWT when both access and refresh are dead. RingCentral auth is rate-limited (about 5 token calls per minute).
  • Call Logs paging is page (1-based) and perPage from 1 to 250 (view=Detailed). Messages and Extensions still page up to 1000.
  • Messages crawl one mailbox page at a time across extensions. Large orgs recrawl one batch per minute until history is done.
  • HTTP 429 responses are retried using Retry-After or RateLimit-Reset.
  • Nested objects are expanded into columns at ingest with Polars json_normalize. The original objects stay in *_json columns so no payload is dropped. Call-log from / to are objects, not arrays. Message-store to and attachments stay arrays in JSON (one row per message). Call-log legs stay in legs_json (not unnested into extra rows). device inside caller info stays a JSON string (from_device / to_device), not exploded. profileImage stays nested JSON.

Stable Detailed-view fields: id, uri, session_id, telephony_session_id, start_time, duration, type, internal_type, direction, action, result, transport, last_modified_time.

Promoted from from / to (CallLogCallerInfo objects): from_phone_number, from_name, from_extension_number, from_location, from_extension_id, from_device, to_phone_number, to_name, to_extension_number, to_location, to_extension_id, to_device.

Promoted from recording when present: recording_id, recording_uri, recording_type, recording_content_uri, recording_duration.

Promoted from the voicemail-link message object when present: message_id, message_type, message_uri.

Promoted from billing: billing_cost_included, billing_cost_purchased.

Promoted from extension: extension_id, extension_uri.

JSON blobs that remain: from_json, to_json (includes nested device), recording_json, message_json, billing_json, extension_json, legs_json (array of legs, not unnested).

Stable fields: id, extension_id, uri, conversation_id, type, direction, availability, read_status, priority, message_status, subject, creation_time, last_modified_time, sms_delivery_time.

Promoted from from (object): from_phone_number, from_name, from_extension_number.

Promoted from to (array, comma-joined, not unnested): to_names, to_phone_numbers.

Promoted from attachments (array, not unnested): attachment_count.

JSON blobs that remain: from_json, to_json, attachments_json.

Stable fields: id, uri, extension_number, name, type, status, hidden, creation_time.

Promoted from contact: email, first_name, last_name, department, company, job_title, business_phone, mobile_phone.

Promoted from permissions: admin_enabled, international_calling_enabled.

JSON blobs that remain: contact_json, permissions_json, profile_image_json (low dashboard value, left nested).

Fields: extension_id (primary key), extension_number, telephony_status, presence_status, user_status, dnd_status, allow_see_my_presence, and observed_at (UTC time Resplendent processed the observation, not the time the phone status changed).

Join extension_id to the Extensions Dataset’s id for employee names and departments, then filter to the support team.

Phone status Meaning
NoCall Not currently on a call
CallConnected Connected to a call
Ringing Phone is ringing
OnHold Call is on hold
ParkedCall Call is parked
Empty Phone status was not provided; treat as unknown

Availability and Do Not Disturb are separate from phone status. NoCall does not mean an employee is available to take a call. Some non-user extension types return Offline without a phone status. Missing phone status is never converted to NoCall.

Polling uses the account presence endpoint instead of one request per employee. RingCentral counts it in the Heavy rate-limit group; actual limits depend on the app and other API usage. Failed or incomplete scheduled pulls do not publish a replacement snapshot.

Fields: queue_member_id (primary key, queue_id:extension_id), queue_id, queue_name, queue_extension_number, extension_id, extension_number, member_name, and observed_at (UTC observation time).

Each row represents one employee in one queue. Employees in multiple queues have a separate row for each membership. Join extension_id to Extensions id or User Presence extension_id for employee details and phone status. Member names may be absent from RingCentral’s membership response; the Extensions Dataset supplies those names.

Includes all Queue Members columns plus accept_queue_calls and accept_current_queue_calls. The first flag describes whether the employee accepts calls through queues generally; the second describes acceptance for this particular queue. An empty flag means unknown, not false. The flags do not indicate whether the employee is on a call; join to User Presence for that status.

Join queue_member_id between the two queue Datasets when comparing membership and acceptance. These are current snapshots, not historical availability or staffing schedules.

Symptom Likely cause What to try
invalid_client Wrong Client ID or Client Secret, or the JWT is not restricted to that app Recopy Client ID/Secret from the same app the JWT is bound to
Expired or revoked JWT JWT was rotated, expired, or the user was disabled Create a new JWT for the Super Admin / service user and paste it
Account call log access denied Token works but company call log returned HTTP 403 Use a Super Admin JWT, or grant Read Account call log
User Presence access denied App or JWT user lacks presence access Add ReadPresence to the app and ReadPresenceStatus to the user’s permissions; refresh the connection and retry
Queue Members or queue discovery access denied App or JWT user cannot read queues Grant ReadAccounts and access to the account’s call queues
Queue Member Presence access denied Queue discovery works but presence access is missing Grant ReadPresence and the corresponding user access, then retry
Throttling / HTTP 429 RingCentral rate limit, including token-endpoint 5/min Wait and retry; do not hammer Save & Test; reduce concurrent API consumers
Incomplete history after save Incremental Datasets recrawl one batch per minute Wait for get_old_rows to finish, or raise the Dataset history setting
Missing expanded call-log columns Dataset columns were saved from an older preview, or parquet was merged before this ingest change Resync Call Logs and re-select columns if the Dataset is pinned to an old list
last_modified_time is empty Call Logs were synced with Simple view, which omits lastModifiedTime Resync Call Logs; this connector now requests view=Detailed
Missing deleted calls or texts Nightly resync has not run since the delete Wait for nightly resync, or resync the Dataset
Sandbox host errors Production credentials used against sandbox, or the reverse Match Environment to the Developer Console app