Authenticate RingCentral with a JWT, per phone server

FeatureVOIPService
Shipped
August 26, 2026 at 1:02 AM UTC
Author
Kamo
Commit
1b8a198

RingCentral was the last provider whose credentials came from a Kamo-wide registration plus two browser consent hops — one for the org, one per member. Both are gone. An organization registers its own app in its own RingCentral developer console with the JWT auth flow enabled, and the client id, secret, platform URL and a service-user JWT live on the VoipProviderInstance that uses them, exactly as FreePBX and Teams credentials already do. One customer's credentials can no longer authorize a call against another customer's account, and there is no consent to obtain, no token to store on a browser's behalf, and no "reconnect" state to fall into. **************** mirrors TeamsInstanceConfigFactory, minus the platform fallback: a JWT is meaningless against anyone else's account, so a fallback could only let a half-configured instance borrow another org's app id and fail later as an opaque 401. require() names the missing field instead. RingCentralJwtTokenService mints access tokens from a JWT and caches them keyed by a digest of the credential, so rotating a JWT is self-evicting and a stale token can never be served for a credential that has been replaced. A 4xx means the credential is dead and asks for a new one; anything else stays retryable so an outage is never mistaken for a revoked JWT. RingCentralProvider is now extension-addressed. Built from the service-user credential it reaches a member at /account/~/extension/{id}/..., so their call history, voicemail, presence and call forwarding work the moment an admin assigns them an extension — previously a member with no OAuth connection got nothing at all. Built from the member's own JWT it addresses ~. Only client-info/sip-provision needs the latter, and asking for it with the account token would not error: it would hand back the SERVICE USER's SIP identity and register the member's softphone as that account. It refuses instead, in the shape the softphone already handles. listExtensions now reads the account's number inventory and carries each extension's own DirectNumber, so a member can be assigned their own line rather than everyone sharing the phone server's main number. Company and main-company numbers are deliberately excluded — treating those as personal would give half the org an identical "direct" number. The webhook provisioner is driven off instances rather than a token table: an org can run more than one RingCentral phone server, and each needs its own subscription against its own platform URL.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing