How to Programmatically Create a RingCentral Device

Tyler Liu
RingCentral Developers
9 min readDec 10, 2019
Learn how to provision a Session Initiation Protocol (SIP) Device using Restful APIs

In this article, I will cover how to programmatically create a RingCentral device. It is essential if you want to create an app to receive/make phone calls. I will divide it into 3 parts:

  • First how to provision a SIP device via Restful API
  • How to register the device via WebSocket & SIP
  • Last how to receive a phone call and get real time audio data via WebRTC

Provision a SIP device via Restful API

First of all what is SIP? SIP is short for Session Initiation Protocol:

The Session Initiation Protocol (SIP) is a signaling protocol used for initiating, maintaining, and terminating real-time sessions that include voice, video and messaging applications.[1] SIP is used for signaling and controlling multimedia communication sessions in applications of Internet telephony for voice and video calls, in private IP telephone systems, in instant messaging over Internet Protocol (IP) networks as well as mobile phone calling over LTE (VoLTE).

Here is the API reference for the API endpoint: https://developers.ringcentral.com/api-reference/SIP/createSIPRegistration

The API endpoint mentioned in the link above can do the following:

Create a SIP registration for a device/application (i.e. WebPhone, Mobile, softphone)

POST /restapi/v1.0/client-info/sip-provision{
"sipInfo": [
{
"transport": "WSS"
}
]
}

We specify “transport”: “WSS”, because we are going to use the WebSocket protocol as a transport for the Session Initiation Protocol (SIP).

Below is a sample API response:

HTTP 200 OK{
"device" : {
"uri" : "https://platform.ringcentral.com/restapi/v1.0/account/11111111/device/22222222",
"id" : "22222222",
"type" : "WebPhone",
"status" : "Online",
"extension" : {
"uri" : "https://platform.ringcentral.com/restapi/v1.0/account/11111111/extension/33333333",
"id" : 33333333,
"extensionNumber" : "115"
},
"phoneLines" : [ {
"lineType" : "Standalone",
"emergencyAddress" : {
"required" : true,
"localOnly" : false
},
"phoneInfo" : {
"id" : 44444444,
"phoneNumber" : "+16508888888",
"paymentType" : "Local",
"type" : "VoiceFax",
"usageType" : "DirectNumber",
"country" : {
"uri" : "https://platform.ringcentral.com/restapi/v1.0/dictionary/country/1",
"id" : "1",
"name" : "United States"
}
}
} ],
"emergencyServiceAddress" : {
"street" : "20 DAVIS DR",
"street2" : "TYLER'S DESK",
"city" : "BELMONT",
"state" : "CA",
"stateId" : "16",
"stateIsoCode" : "CA",
"stateName" : "California",
"country" : "US",
"countryId" : "1",
"countryIsoCode" : "US",
"countryName" : "United States",
"zip" : "94002",
"customerName" : "Tyler Liu",
"outOfCountry" : false
},
"linePooling" : "Guest"
},
"sipInfo" : [ {
"transport" : "WSS",
"username" : "16506666666*115",
"password" : "0XeL9qcf",
"authorizationId" : "22222222",
"domain" : "sip.ringcentral.com",
"outboundProxy" : "sip111-222.ringcentral.com:8083",
"outboundProxyBackup" : "sip333-444.ringcentral.com:8083"
} ],
"sipInfoPstn" : [ ],
"sipFlags" : {
"voipFeatureEnabled" : true,
"voipCountryBlocked" : false,
"outboundCallsEnabled" : true,
"dscpEnabled" : false,
"dscpSignaling" : 26,
"dscpVoice" : 46,
"dscpVideo" : 34
},
"sipErrorCodes" : [ "408" ]
}

Please note that we will only take advantage of the sipInfo part, but depending on your use case you may also need the device.id .

Register the device via WebSocket & SIP

Why do we need a WebSocket? This is because we are taking advantage of the WebSocket protocol as a a transport for the Session Initiation Protocol (SIP).

I will use JavaScript to show you how to do this. Other languages are also viable as long as they can do a WebSocket.

const ws = new WebSocket('wss://' + sipInfo.outboundProxy, 'sip', { rejectUnauthorized: false })

The WebSocket server domain is sipInfo.outboundProxy, the subprotocol is sip, and since we don’t have a SSL certificate in the development environment, we specify rejectUnauthorized: false .

We send a SIP register request like this:

REGISTER sip:sip.ringcentral.com SIP/2.0
Call-ID: 980eeef0-90c4-4284-923d-7a0cf17697e9
User-Agent: ringcentral-softphone-demo
Contact: <sip:618bb92b-f0de-40ce-8b12-c9e8349c2759@c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;transport=ws>;expires=600
Via: SIP/2.0/WSS c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;branch=z9hG4bKbd7cd554-f01f-409a-a65a-f929aff7438b
From: <sip:16506666666*115@sip.ringcentral.com>;tag=59945417-eb71-4986-a217-4e3315c72282
To: <sip:16506666666*115@sip.ringcentral.com>
CSeq: 8082 REGISTER
Content-Length: 0
Max-Forwards: 70

For the unique identifier, I recommend using a UUID, which creates a unique, random string like980eeef0–90c4–4284–923d-7a0cf17697e9. You can choose to use other random strings or create your own string, but it’s important to make sure that string is unique and there are no duplicates.

Note: With UUID there is still a small risk of duplication which can cause data collisions like it did in this case. We won’t be going into details about UUID’s in this article — so if you’re interested in learning more check out this article.

16506666666*115 is sipInfo.username , sip.ringcentral.com is sipInfo.domain .

The server will immediately respond with the below response.

SIP/2.0 100 Trying
Via: SIP/2.0/WSS c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;branch=z9hG4bKbd7cd554-f01f-409a-a65a-f929aff7438b
From: <sip:16506666666*115@sip.ringcentral.com>;tag=59945417-eb71-4986-a217-4e3315c72282
To: <sip:16506666666*115@sip.ringcentral.com>
Call-ID: 980eeef0-90c4-4284-923d-7a0cf17697e9
CSeq: 8082 REGISTER
Content-Length: 0

This will be followed by the below response.

SIP/2.0 401 Unauthorized
Via: SIP/2.0/WSS c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;branch=z9hG4bKbd7cd554-f01f-409a-a65a-f929aff7438b
From: <sip:16506666666*115@sip.ringcentral.com>;tag=59945417-eb71-4986-a217-4e3315c72282
To: <sip:16506666666*115@sip.ringcentral.com>;tag=awE1o7
Call-ID: 980eeef0-90c4-4284-923d-7a0cf17697e9
CSeq: 8082 REGISTER
Content-Length: 0
Www-Authenticate: Digest realm="sip.ringcentral.com", nonce="XbhxHV24b/HjGUNFJl82du25ferrxpaG"

SIP/2.0 401 Unauthorized means authorization is required for the registration. And the message also includes a nonce:

Www-Authenticate: Digest realm="sip.ringcentral.com", nonce="XbhxHV24b/HjGUNFJl82du25ferrxpaG"

In order to authorize successfully, we need the credentials in sipInfo:

{
"transport" : "WSS",
"username" : "16506666666*115",
"password" : "0XeL9qcf",
"authorizationId" : "22222222",
"domain" : "sip.ringcentral.com",
"outboundProxy" : "sip111-222.ringcentral.com:8083",
"outboundProxyBackup" : "sip333-444.ringcentral.com:8083"
}

We need to send a register request again:

REGISTER sip:sip.ringcentral.com SIP/2.0
From: <sip:16506666666*115@sip.ringcentral.com>;tag=59945417-eb71-4986-a217-4e3315c72282
To: <sip:16506666666*115@sip.ringcentral.com>
CSeq: 8083 REGISTER
Call-ID: 980eeef0-90c4-4284-923d-7a0cf17697e9
User-Agent: ringcentral-softphone-go
Authorization: Digest algorithm=MD5, username="22222222", realm="sip.ringcentral.com", nonce="XbhxHV24b/HjGUNFJl82du25ferrxpaG", uri="sip:sip.ringcentral.com", response="1777104705dad54224612d1655c1bbbd"
Contact: <sip:618bb92b-f0de-40ce-8b12-c9e8349c2759@c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;transport=ws>;expires=600
Via: SIP/2.0/WSS c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;branch=z9hG4bK0401bc18-fcda-4f91-a8e4-979c5f0eb325
Content-Length: 0
Max-Forwards: 70

This time, the register request is almost the same as last time, but with an extra line:

Authorization: Digest algorithm=MD5, username="22222222", realm="sip.ringcentral.com", nonce="XbhxHV24b/HjGUNFJl82du25ferrxpaG", uri="sip:sip.ringcentral.com", response="1777104705dad54224612d1655c1bbbd"

Please note that, username above is sipInfo.authorizationId. Every data field seems pretty straightforward, but you might be wondering about this string in response=”1777104705dad54224612d1655c1bbbd”?

There is an algorithm to generate the response data:

const generateResponse = (username, password, realm, method, uri, nonce) => {
const ha1 = md5(username + ':' + realm + ':' + password)
const ha2 = md5(method + ':' + uri)
const response = md5(ha1 + ':' + nonce + ':' + ha2)
return response
}

username is sipInfo.authorizationId , password is sipInfo.password, realm is sipInfo.domain, the method should be string REGISTER, uri should be sip: + realm.

If you provided the credentials information correctly, the WebSocket server should respond as follows:

SIP/2.0 100 Trying
Via: SIP/2.0/WSS c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;branch=z9hG4bK0401bc18-fcda-4f91-a8e4-979c5f0eb325
From: <sip:16506666666*115@sip.ringcentral.com>;tag=59945417-eb71-4986-a217-4e3315c72282
To: <sip:16506666666*115@sip.ringcentral.com>
Call-ID: 980eeef0-90c4-4284-923d-7a0cf17697e9
CSeq: 8083 REGISTER
Content-Length: 0
SIP/2.0 200 OK
Via: SIP/2.0/WSS c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;branch=z9hG4bK0401bc18-fcda-4f91-a8e4-979c5f0eb325
From: <sip:16506666666*115@sip.ringcentral.com>;tag=59945417-eb71-4986-a217-4e3315c72282
To: <sip:16506666666*115@sip.ringcentral.com>;tag=1YCdDC
Call-ID: 980eeef0-90c4-4284-923d-7a0cf17697e9
CSeq: 8083 REGISTER
Content-Length: 0
Contact: <sip:618bb92b-f0de-40ce-8b12-c9e8349c2759@c11fd39f-4876-4e07-b23c-4b75e661df11.invalid;transport=ws>;expires=56

Receive a phone call and get real time audio data via WebRTC

In this part I will cover how to receive inbound calls. Outbound calls are out of scope for this article, but readers are encouraged to give it a try as an exercise.

Whenever there is an inbound call, you will receive a WebSocket invite SIP message like this:

INVITE sip:ccd253ff-4739-4b71-aa55-44b32389efa7@fba3ccc7-f58e-4f2f-8bc3-93f6ae418fe1.invalid;transport=ws SIP/2.0
Via: SIP/2.0/WSS 104.245.57.165:8083;rport;branch=z9hG4bK2h1boP-aMTQlj
From: "WIRELESS CALLER" <sip:+16509999999@104.245.57.165>;tag=10.13.121.68-5070-e02ad7dc192e48
To: "WIRELESS CALLER" <sip:16506666666*115@50.237.72.154>
Call-ID: 366abc5e1920429bb3e894f60f9388c1
CSeq: 316586109 INVITE
Max-Forwards: 67
Content-Length: 873
Contact: <sip:+16509999999@104.245.57.165:8083;transport=wss>
Content-Type: application/sdp
User-Agent: RC_SIPWRP_121.68
p-rc-api-ids: party-id=p-85ea49e2ef8c40b49be996022dd08916-2;session-id=s-85ea49e2ef8c40b49be996022dd08916
p-rc-api-call-info: callAttributes=reject,send-vm
P-rc: <Msg><Hdr SID="35488554330848" Req="{F27BF503-9AE3-42FD-AF3C-FF74A243B317}" From="#1336016@sip.ringcentral.com:5060" To="16506666666*115" Cmd="6"/><Bdy SrvLvl="-149699523" SrvLvlExt="406" Phn="+16509999999" Nm="WIRELESS CALLER" ToPhn="+16504223279" ToNm="Tyler Liu" RecUrl=""/></Msg>
Call-Info: <1377864009_31373420@10.14.116.50>;purpose=info
v=0
o=- 6839181294428951775 6083578892471156205 IN IP4 104.245.57.182
s=SmcSip
c=IN IP4 104.245.57.182
t=0 0
m=audio 52290 RTP/SAVPF 109 111 18 0 8 9 96 101
a=rtpmap:109 OPUS/16000
a=fmtp:109 useinbandfec=1
a=rtcp-fb:109 ccm tmmbr
a=rtpmap:111 OPUS/48000/2
a=fmtp:111 useinbandfec=1
a=rtcp-fb:111 ccm tmmbr
a=rtpmap:18 g729/8000
a=fmtp:18 annexb=no
a=rtpmap:0 pcmu/8000
a=rtpmap:8 pcma/8000
a=rtpmap:9 g722/8000
a=rtpmap:96 ilbc/8000
a=fmtp:96 mode=20
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=sendrecv
a=rtcp:52291
a=rtcp-mux
a=setup:actpass
a=fingerprint:sha-1 DA:CD:46:47:70:D5:C4:56:BA:E7:89:AE:62:F7:B7:DC:54:9E:7C:15
a=ice-ufrag:2SVPZvhD
a=ice-pwd:7pbIMi04pehlahhmYDidlc0z8d
a=candidate:Ypx7Hg43vRXBgO0w 1 UDP 2130706431 104.245.57.182 52290 typ host
=candidate:Ypx7Hg43vRXBgO0w 2 UDP 2130706430 104.245.57.182 52291 typ host

We can interpret the invite message as three parts: the subject, the headers and the body. The first line is the subject: INVITE sip:ccd253ff-4739–4b71-aa55–44b32389efa7@fba3ccc7-f58e-4f2f-8bc3–93f6ae418fe1.invalid;transport=ws SIP/2.0 . The remaining text is the headers and the body which are separated by one blank line.

After your app gets this message, you should respond to the server with the following two messages. This is in order to let the server know that the phone call has been delivered:

SIP/2.0 180 Ringing
Via: SIP/2.0/WSS 104.245.57.165:8083;rport;branch=z9hG4bK2h1boP-aMTQlj
From: "WIRELESS CALLER" <sip:+16509999999@104.245.57.165>;tag=10.13.121.68-5070-e02ad7dc192e48
Call-ID: 366abc5e1920429bb3e894f60f9388c1
CSeq: 316586109 INVITE
Contact: <sip:fba3ccc7-f58e-4f2f-8bc3-93f6ae418fe1.invalid;transport=ws>
Supported: outbound
To: "WIRELESS CALLER" <sip:16506666666*115@50.237.72.154>;tag=d90da9de-e261-45ec-b2cb-781e484a51de
Content-Length: 0
MESSAGE sip:#1336016@sip.ringcentral.com:5060 SIP/2.0
From: <sip:16506666666*115@sip.ringcentral.com>;tag=a3e356b9-d9e5-4962-a4b0-6013941f821e
To: <sip:#1336016@sip.ringcentral.com:5060>
Content-Type: x-rc/agent
CSeq: 8084 MESSAGE
Call-ID: 443c6592-4708-4f91-8bed-4af03304c24e
User-Agent: ringcentral-softphone-go
Via: SIP/2.0/WSS fba3ccc7-f58e-4f2f-8bc3-93f6ae418fe1.invalid;branch=z9hG4bK0d18207a-96f5-4ff0-94f5-0908e48da9ad
Content-Length: 179
Max-Forwards: 70
<Msg><Hdr SID="35488554330848" Req="{F27BF503-9AE3-42FD-AF3C-FF74A243B317}" From="16506666666*115" To="#1336016@sip.ringcentral.com:5060" Cmd="17"/><Bdy Cln="802398776016"/></Msg>

The server will reply with the following confirmation messages:

SIP/2.0 100 Trying
Via: SIP/2.0/WSS fba3ccc7-f58e-4f2f-8bc3-93f6ae418fe1.invalid;branch=z9hG4bK0d18207a-96f5-4ff0-94f5-0908e48da9ad
From: <sip:16506666666*115@sip.ringcentral.com>;tag=a3e356b9-d9e5-4962-a4b0-6013941f821e
To: <sip:#1336016@sip.ringcentral.com:5060>
Call-ID: 443c6592-4708-4f91-8bed-4af03304c24e
CSeq: 8084 MESSAGE
Content-Length: 0


SIP/2.0 200 OK
Via: SIP/2.0/WSS fba3ccc7-f58e-4f2f-8bc3-93f6ae418fe1.invalid;branch=z9hG4bK0d18207a-96f5-4ff0-94f5-0908e48da9ad
From: <sip:16506666666*115@sip.ringcentral.com>;tag=a3e356b9-d9e5-4962-a4b0-6013941f821e
To: <sip:%231336016@sip.ringcentral.com>;tag=z9hG4bKd3d1.44136a0737a50ff837de5c6a67c40e73.0
Call-ID: 443c6592-4708-4f91-8bed-4af03304c24e
CSeq: 8084 MESSAGE
Content-Length: 0

Now it’s time to setup the WebRTC. You will need to have some basic knowledge of WebRTC:

WebRTC is a free, open project that provides browsers and mobile applications with Real-Time Communications (RTC) capabilities via simple APIs. The WebRTC components have been optimized to best serve this purpose.

Most WebRTC applications run in a browser, but not all of them. Here we run the app as a non-browser app on the desktop. Other than the official WebRTC implementation, there are ports or rewrites in other programming languages, such as

In this article, we use the node-webrtc:

const { RTCSessionDescription, RTCPeerConnection, nonstandard: { RTCAudioSink } } = require('wrtc')const remoteRtcSd = new RTCSessionDescription({ type: 'offer', sdp: inviteSipMessage.body })
const peerConnection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:74.125.194.127:19302' }] })
peerConnection.setRemoteDescription(remoteRtcSd)
const localRtcSd = await peerConnection.createAnswer()
peerConnection.setLocalDescription(localRtcSd)

We then reply to the WebSocket server as follows:

SIP/2.0 200 OK
Via: SIP/2.0/WSS 104.245.57.183:8083;rport;branch=z9hG4bK3cJPyQ-2k8oGf
From: "WIRELESS CALLER" <sip:+16509999999@104.245.57.183>;tag=10.13.20.192-5070-92f54148ec0f4a
Call-ID: 86a01ac4cb8341edb2d337af4a3877a7
CSeq: 316618655 INVITE
Content-Type: application/sdp
Contact: <sip:ec3a2c52-bfe0-4558-ad52-32ce8a8faa1e@f3445dc1-ba3a-4af7-a94a-7050d642b60c.invalid;transport=ws>
Supported: outbound
To: "WIRELESS CALLER" <sip:16506666666*115@50.237.72.154>;tag=b0c8864c-92e5-4c5f-913d-5f63cf61d3d3
Content-Length: 971
v=0
o=- 418086535 1572373109 IN IP4 0.0.0.0
s=-
t=0 0
a=fingerprint:sha-256 4C:8A:10:32:67:E4:8E:37:21:E8:12:06:BE:FD:1F:51:6C:22:F8:EA:85:C2:31:E0:E9:7E:02:6A:31:12:87:4E
a=group:BUNDLE 0
m=audio 9 UDP/TLS/RTP/SAVPF 0
c=IN IP4 0.0.0.0
a=setup:passive
a=mid:0
a=ice-ufrag:imvfPcTtTWNeEfUo
a=ice-pwd:LiBRXpuJRtHqNLrPIPGzUZDGUEfvBtQG
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:0 PCMU/8000
a=ssrc:920256325 cname:XLpsmtKjHxiUgeaP
a=ssrc:920256325 msid:XLpsmtKjHxiUgeaP gzAfQNwfIGAYTxSh
a=ssrc:920256325 mslabel:XLpsmtKjHxiUgeaP
a=ssrc:920256325 label:gzAfQNwfIGAYTxSh
a=msid:XLpsmtKjHxiUgeaP gzAfQNwfIGAYTxSh
a=sendrecv
a=candidate:foundation 1 udp 2130706431 100.64.0.1 65022 typ host generation 0
a=candidate:foundation 2 udp 2130706431 100.64.0.1 65022 typ host generation 0
a=candidate:foundation 1 udp 2130706431 10.32.21.97 60380 typ host generation 0
a=candidate:foundation 2 udp 2130706431 10.32.21.97 60380 typ host generation 0
=end-of-candidates

Please note that, the body of the message above is localRtcSd.sdp .

The server will send you confirmations like these:

ACK sip:ec3a2c52-bfe0-4558-ad52-32ce8a8faa1e@f3445dc1-ba3a-4af7-a94a-7050d642b60c.invalid;transport=ws SIP/2.0
Via: SIP/2.0/WSS 104.245.57.183:8083;rport;branch=z9hG4bK3lGSg1-2vKTaF
From: "WIRELESS CALLER" <sip:+16509999999@104.245.57.183>;tag=10.13.20.192-5070-92f54148ec0f4a
To: "WIRELESS CALLER" <sip:16506666666*115@50.237.72.154>;tag=b0c8864c-92e5-4c5f-913d-5f63cf61d3d3
Call-ID: 86a01ac4cb8341edb2d337af4a3877a7
CSeq: 316618655 ACK
Max-Forwards: 69
Content-Length: 0
Contact: <sip:+16509999999@104.245.57.183:8083;transport=wss>
User-Agent: RC_SIPWRP_20.192
MESSAGE sip:ec3a2c52-bfe0-4558-ad52-32ce8a8faa1e@f3445dc1-ba3a-4af7-a94a-7050d642b60c.invalid;transport=ws SIP/2.0
Via: SIP/2.0/WSS 104.245.57.183:8083;rport;branch=z9hG4bK3UOkcD-3b1hfO
From: <sip:%231084016@sip.ringcentral.com>;tag=2e7c0305dd944eebbb0bd8c3e7f82f7a
To: <sip:16506666666*115@sip.ringcentral.com>
Call-ID: bac79da0380b424ca73d1019bf78985c
CSeq: 316618845 MESSAGE
Max-Forwards: 67
Content-Length: 200
Content-Type: x-rc/agent
<Msg><Hdr SID="35488568468848" Req="" From="#1084016@sip.ringcentral.com:5060" To="16506666666*115@sip.ringcentral.com:5060" Cmd="7"/><Bdy Cln="802398772016" IP="4294967295" Sts="0" CtrlCln=""/></Msg>

We’ve finished all the SIP traffic and now it’s time to get the audio data via the WebRTC:

peerConnection.addEventListener('track', e => {
const audioSink = new RTCAudioSink(e.track)
const audioPath = 'audio.raw'
if (fs.existsSync(audioPath)) {
fs.unlinkSync(audioPath)
}
const stream = fs.createWriteStream(audioPath, { flags: 'a' })
audioSink.ondata = data => {
stream.write(Buffer.from(data.samples.buffer))
}
})

With the code above, we were able to get real time audio data and we saved the data to file audio.raw. If you’d like to play the saved audio, you can try:

play -b 16 -e signed -c 1 -r 48000 audio.raw

Please note that, your parameters may vary, depending on your audio format and quality. The play command I used is from the sox project.

Summary

In this article, we built a RingCentral device from scratch using Restful API to provision a device, we used SIP over WebSocket to register the device and finally we used SIP and WebRTC to answer an inbound call and get audio data.

Sample Implementations

Thank you for reading, if you like this article, don’t forget to clap!

--

--