Webhooks Overview

The Webhooks service provides customers with near real-time events corresponding to their usage of the OnSIP platform. These events are delivered via an HTTP POST to a web server of the customer’s choice.

Subscriptions

Events are delivered to a URL on a subscription basis. Subscriptions have a "time to live" (TTL) of 24 hours by default, and the subscribing application must resubscribe at least every 24 hours to continue receiving events. A subscription can be created via an HTTP request to the OnSIP Admin API. If you are unfamiliar with the Admin API, please refer to this page.

A subscription can be only created, renewed, deleted, or read. Instead of changing the properties of a subscription, delete the existing one and create a new one.

Creating a Subscription

A subscription is created using the WebhookSubscriptionAdd API action. The parameters are as follows:

Parameter Description
Action "WebhookSubscriptionAdd"
OrganizationId The organization for which this subscription applies.
Name The name of the subscription which will be visible on the Admin Portal.
TargetUrl Events are sent via an HTTP POST to this URL. The URL must be HTTPS. Note: We test the validity of the URL by POSTing to the URI with an empty payload.
SslVerify Verify that the server hosted at targetUrl has a valid certificate. Valid entries are "true" and "false." Note: We perform SSL verification both at the time of subscription creation and every time that we POST an event. An empty JSON object ("{}") is POSTed at the time of subscription creation.
Version (optional) The Webhooks event version. Defaults to 1.0.
Sample Request

cURL

1
2
3
4
curl -X POST \
    --data \
    'Action=WebhookSubscriptionAdd&SessionId=iusj9dslgivu5vqsm28qq330k6&OrganizationId=26107&Name=Example Subscription&TargetUrl=https://www.example.com/webhooks-app&SslVerify=true' \
    https://api.onsip.com/api

JavaScript

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
var data = new FormData();
data.append('Action', 'WebhookSubscriptionAdd');
data.append('SessionId', 'iusj9dslgivu5vqsm28qq330k6');
data.append('OrganizationId', '26107');
data.append('Name', 'Example Subscription');
data.append('TargetUrl', 'https://www.example.com/webhooks-app');
data.append('SslVerify', 'true');

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.onsip.com/api', true);
xhr.onload = function () {
  console.log(this.responseText);
}
xhr.send(data);

The response by the API will contain the attributes of the subscription, as well as aWebhookSubscriptionId, which uniquely identifies this subscription amongst all subscriptions. The Expired attribute corresponds to the time at which a subscription will expire, as in we will stop sending events to the TargetUrl. The expiration date will default to 24 hours from the time of initial subscription. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.jnctn.net/ns/rest/2006-01">
  <Context>
    <Action>
      <IsCompleted>true</IsCompleted>
    </Action>
    <Request>
      <IsValid>true</IsValid>
      <DateTime>2017-09-19T18:42:00+00:00</DateTime>
      <Duration>470</Duration>
      <Parameters>
        <Parameter>
          <Name>Action</Name>
          <Value>WebhookSubscriptionAdd</Value>
        </Parameter>
        <Parameter>
          <Name>SessionId</Name>
          <Value>iusj9dslgivu5vqsm28qq330k6</Value>
        </Parameter>
        <Parameter>
          <Name>Name</Name>
          <Value>Example Subscription</Value>
        </Parameter>
        <Parameter>
          <Name>OrganizationId</Name>
          <Value>26107</Value>
        </Parameter>
        <Parameter>
          <Name>TargetUrl</Name>
          <Value>https://www.example.com/webhooks-app</Value>
        </Parameter>
        <Parameter>
          <Name>SslVerify</Name>
          <Value>true</Value>
        </Parameter>
      </Parameters>
    </Request>
    <Session>
      <IsEstablished>true</IsEstablished>
      <SessionId>iusj9dslgivu5vqsm28qq330k6</SessionId>
      <UserId>152255</UserId>
      <Roles>
        <Role>
          <Name>Account Admin</Name>
        </Role>
      </Roles>
    </Session>
  </Context>
  <Result>
    <WebhookSubscriptionAdd>
      <WebhookSubscription>
        <WebhookSubscriptionId>ePXyDODmvAF77Vlv</WebhookSubscriptionId>
        <OrganizationId>26107</OrganizationId>
        <AccountId>23568</AccountId>
        <Domain>example.onsip.com</Domain>
        <Name>Example Subscription</Name>
        <Version>1.0</Version>
        <TargetUrl>https://www.example.com/webhooks-app</TargetUrl>
        <SslVerify>true</SslVerify>
        <Expired>2017-09-20T18:42:01+00:00</Expired>
        <Modified>2017-09-19T18:42:01+00:00</Modified>
        <Created>2017-09-19T18:42:01+00:00</Created>
      </WebhookSubscription>
    </WebhookSubscriptionAdd>
  </Result>
</Response>

Renewing a Subscription

A subscription's lifetime can be extended by using the same WebhookSubscriptionRenew API action. This will update the expiration date to be exactly 24 hours from the time of the renewal.

Param Description
Action "WebhookSubscriptionRenew"
WebhookSubscriptionId Unique subscription identifier.
Sample Request

cURL

1
2
3
4
curl -X POST \
    --data \
    'Action=WebhookSubscriptionRenew&SessionId=iusj9dslgivu5vqsm28qq330k6&WebhookSubscriptionId=ePXyDODmvAC77V1v' \
https://api.onsip.com/api

JavaScript

1
2
3
4
5
6
7
8
9
10
11
var data = new FormData();
data.append('Action', 'WebhookSubscriptionRenew');
data.append('SessionId', 'iusj9dslgivu5vqsm28qq330k6');
data.append('WebhookSubscriptionId', 'ePXyDODmvAC77V1v');

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.onsip.com/api', true);
xhr.onload = function () {
  console.log(this.responseText);
}
xhr.send(data);
Sample Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.jnctn.net/ns/rest/2006-01">
  <Context>
    <Action>
      <IsCompleted>true</IsCompleted>
    </Action>
    <Request>
      <IsValid>true</IsValid>
      <DateTime>2017-09-19T19:03:39+00:00</DateTime>
      <Duration>139</Duration>
      <Parameters>
        <Parameter>
          <Name>Action</Name>
          <Value>WebhookSubscriptionRenew</Value>
        </Parameter>
        <Parameter>
          <Name>SessionId</Name>
          <Value>iusj9dslgivu5vqsm28qq330k6</Value>
        </Parameter>
        <Parameter>
          <Name>WebhookSubscriptionId</Name>
          <Value>ePXyDODmvAC77V1v</Value>
        </Parameter>
      </Parameters>
    </Request>
    <Session>
      <IsEstablished>true</IsEstablished>
      <SessionId>iusj9dslgivu5vqsm28qq330k6</SessionId>
      <UserId>358611</UserId>
      <Roles>
        <Role>
          <Name>Account Admin</Name>
        </Role>
      </Roles>
    </Session>
  </Context>
  <Result>
    <WebhookSubscriptionRenew>
      <WebhookSubscription>
        <WebhookSubscriptionId>ePXyDODmvAC77Vlv</WebhookSubscriptionId>
        <OrganizationId>26107</OrganizationId>
        <AccountId>23568</AccountId>
        <Domain>example.onsip.com</Domain>
        <Name>Example Subscription</Name>
        <Version>1.0</Version>
        <TargetUrl>https://www.example.com/webhooks-app</TargetUrl>
        <SslVerify>true</SslVerify>
        <Expired>2017-09-20T18:42:01+00:00</Expired>
        <Modified>2017-09-19T18:42:01+00:00</Modified>
        <Created>2017-09-19T18:42:01+00:00</Created>
      </WebhookSubscription>
    </WebhookSubscriptionRenew>
  </Result>
</Response>

Reading a Subscription

Param Description
Action "WebhookSubscriptionRead"
WebhookSubscriptionId Unique subscription identifier.
Sample Request

cURL

1
2
3
4
curl -X POST \
    --data \
    'Action=WebhookSubscriptionRead&SessionId=iusj9dslgivu5vqsm28qq330k6&WebhookSubscriptionId=ePXyDODmvAC77V1v' \
    https://api.onsip.com/api

JavaScript

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
var data = new FormData();
data.append('Action', 'WebhookSubscriptionRead');
data.append('SessionId', 'iusj9dslgivu5vqsm28qq330k6');
data.append('WebhookSubscriptionId', 'ePXyDODmvAC77V1v');

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.onsip.com/api', true);
xhr.onload = function () {
  console.log(this.responseText);
}
xhr.send(data);
Sample Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.jnctn.net/ns/rest/2006-01">
  <Context>
    <Action>
      <IsCompleted>true</IsCompleted>
    </Action>
    <Request>
      <IsValid>true</IsValid>
      <DateTime>2017-09-19T19:03:39+00:00</DateTime>
      <Duration>139</Duration>
      <Parameters>
        <Parameter>
          <Name>Action</Name>
          <Value>WebhookSubscriptionRead</Value>
        </Parameter>
        <Parameter>
          <Name>SessionId</Name>
          <Value>iusj9dslgivu5vqsm28qq330k6</Value>
        </Parameter>
        <Parameter>
          <Name>WebhookSubscriptionId</Name>
          <Value>ePXyDODmvAC77V1v</Value>
        </Parameter>
      </Parameters>
    </Request>
    <Session>
      <IsEstablished>true</IsEstablished>
      <SessionId>iusj9dslgivu5vqsm28qq330k6</SessionId>
      <UserId>358611</UserId>
      <Roles>
        <Role>
          <Name>Account Admin</Name>
        </Role>
      </Roles>
    </Session>
  </Context>
  <Result>
    <WebhookSubscriptionRead>
      <WebhookSubscription>
        <WebhookSubscriptionId>ePXyDODmvAC77Vlv</WebhookSubscriptionId>
        <OrganizationId>26107</OrganizationId>
        <AccountId>23568</AccountId>
        <Domain>example.onsip.com</Domain>
        <Name>Example Subscription</Name>
        <Version>1.0</Version>
        <TargetUrl>https://www.example.com/webhooks-app</TargetUrl>
        <SslVerify>true</SslVerify>
        <Expired>2017-09-20T18:42:01+00:00</Expired>
        <Modified>2017-09-19T18:42:01+00:00</Modified>
        <Created>2017-09-19T18:42:01+00:00</Created>
      </WebhookSubscription>
    </WebhookSubscriptionRead>
  </Result>
</Response>

Deleting a Subscription

Param Description
Action "WebhookSubscriptionDelete"
WebhookSubscriptionId Unique subscription identifier.
Sample Request

cURL

1
2
3
4
curl -X POST \
    --data \
    'Action=WebhookSubscriptionDelete&WebhookSubscriptionId=ePXyDODmvAC77V1v' \
    https://api.onsip.com/api

JavaScript

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
var data = new FormData();
data.append('Action', 'WebhookSubscriptionDelete');
data.append('SessionId', 'iusj9dslgivu5vqsm28qq330k6');
data.append('WebhookSubscriptionId', 'ePXyDODmvAC77Vlv');

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.onsip.com/api', true);
xhr.onload = function () {
  console.log(this.responseText);
}
xhr.send(data);
Sample Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.jnctn.net/ns/rest/2006-01">
  <Context>
    <Action>
      <IsCompleted>true</IsCompleted>
    </Action>
    <Request>
      <IsValid>true</IsValid>
      <DateTime>2017-09-19T19:31:22+00:00</DateTime>
      <Duration>117</Duration>
      <Parameters>
        <Parameter>
          <Name>Action</Name>
          <Value>WebhookSubscriptionDelete</Value>
        </Parameter>
        <Parameter>
          <Name>SessionId</Name>
          <Value>iusj9dslgivu5vqsm28qq330k6</Value>
        </Parameter>
        <Parameter>
          <Name>WebhookSubscriptionId</Name>
          <Value>ePXyDODmvAC77Vlv</Value>
        </Parameter>
      </Parameters>
    </Request>
    <Session>
      <IsEstablished>true</IsEstablished>
      <SessionId>iusj9dslgivu5vqsm28qq330k6</SessionId>
      <UserId>358611</UserId>
      <Roles>
        <Role>
          <Name>Account Admin</Name>
        </Role>
      </Roles>
    </Session>
  </Context>
  <Result>
    <WebhookSubscriptionDelete />
  </Result>
</Response>

Events

For the simplest call scenario, the high level picture is easy to create from the series of events. Consider this: An individual places a call from their cell phone (over the PSTN) to a DID associated with a user.

The events that are generated would minimally include “dialog confirmed” and “dialog terminated.” It can be more natural to think of a call in terms of state, for example: "No Call," "On Call," and "End of Call." Each event then corresponds to a state transition (refer to Figure 1). To help correlate a collection of call events, each event specifies a callId. Figure 2 expands the picture to include ringing, a failed call (no answer) or a successfully recorded call.

Webhooks - Simple State Diagram.png
Figure 1 - A simple state diagram.
Webhooks - Expanded State Diagram.png
Figure 2 - An expanded state diagram.

The example provided above involves only a single call. However, it is possible for a single user to take part in multiple calls throughout an interaction on our platform. To help associate the calls, the events contain a streamId, which identifies a collection of events resulting from an individual interacting with one or many individuals on the OnSIP platform. The streamId is best explained through examaple.

Consider this scenario: Alice is trying to reach her colleague Bob, who works at a company called Webhooks Are Fun, Inc. (WAF Inc. for short). During the call, Alice realizes she needs to speak with Bob's intern, Fred. Here is the sequence of events:

  • Alice calls Bob's DID over the PSTN, generating a "dialog created" event with a new streamId and a new callId.
  • Bob answers the phone, generating a "dialog confirmed" event with the same IDs.
  • Bob blind transfers the call to Fred, ending his dialog with Alice and beginning a new dialog between Alice and Fred. A "dialog referred" event and a "dialog terminated" event are generated with the existing callId, and a "dialog requested" event is generated with a new callId.
  • Fred answers the phone, generating a "dialog confirmed" event.
  • Alice and Fred chat for a bit, then Fred hangs up the phone, generating a "dialog terminated" event.

Now here's the important point: our system treats the conversation between Alice and Bob and the conversation between Alice and Fred as independent calls. Each call has a unique callId and can generate a complete set of events (“dialog requested,” “dialog confirmed,” etc.) and by extension transition through each call state. All of the events, however, will contain exactly the same streamId. In other words, no matter how many times Alice is transferred to another user within Bob and Fred’s organization, events will be generated with the same streamId.

Let's say that WAF Inc. has an Attendant Menu that is configured with a DID, and when a customer calls they have the option to enter an employee's extension. Here is the sequence of events:

  • Alice calls WAF Inc.'s DID over the PSTN and connects immediately, generating a "dialog created" event with a new streamId and a new callId. A "dialog confirmed" event is generated immediately after the "dialog created" event.
  • Alice dials x7001 (Bob's extension). The Attendant Menu performs a blind transfer, ending its dialog with Alice and beginning a new dialog between Alice and Bob. A "dialog terminated" event and a "dialog referred" event are generated with the existing callId, and a "dialog requested" event is generated with a new callId.
  • Bob picks up the phone and connects with Alice, generating a "dialog confirmed" event.
  • Bob and Alice chat for a bit, then Alice hangs up the phone, generating a "dialog terminated" event.
  • The recording of the conversation between Bob and Alice is uploaded to cloud storage, generating a "recording uploaded" event.

The diagram below (Figure 3) highlights state transitions of the stream described above.

Webhooks - Transfer State Diagram.pngFigure 3 - A state diagram with a transfer. The bolded states and arrows identify the paths that are actually taken in the example above.

As you can see, the previous two examples fire nearly identical events. The only difference is that the call between the customer and the Attendant Menu is not recorded. As more endpoints are introduced (Smart Queues, Dial by Name Directories, etc.), the sequence of events will follow a similar pattern.

Packet Details

Events are delivered via an HTTP/1.1 POST. The fields are as follows:

Key Value
id Uniquely identifies a single event.
streamId Uniquely identifies a stream of events (described in more detail above).
subscriptionId Uniquely identifies a subscription sending events to single URL.
version The Webhooks event version. Determines the expected format of this packet and payload.
type The type of the event.
payload An object containing type-specific information.
createdAt Date-Time at which the event was generated.

The de facto reference for the event packet/payload format will always be available via the Webhook Schema github page. We provide up-to-date JSON Schemas for each packet type, and these schemas are made available to be used by client applications wishing to validate the data that we send. Every event contains the same fields, but the fields contained in the payload object are dependent upon type. We make the payload fields for some types available below for convenience, but as stated above the Webhook Schema github page is the best reference.

Some types have the same payload formats, and they are grouped together below.

call.dialog.created
call.dialog.confirmed
call.dialog.terminated
call.dialog.failed

Key Value
callId Uniquely identifies the call.
fromUri URI of the caller.
toUri URI of the callee.

Example:

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
{
    "id": "189004747f808d1fd2c4b848480fa270",
    "streamId": "1cd4606b-4c84-45b2-80f8-9318e7aea112",
    "subscriptionId": "ePXyDODmvAC77Vlv",
    "type": "call.dialog.confirmed",
    "payload": {
        "callId": "8b41c365-11d8-1236-619d-5254002c49e7",
        "toUri": "sip:alice@foo.onsip.com",
        "fromUri": "sip:11238036862@jnctn.net"
    },
    "createdAt": "2017-09-11T21:10:58.735641Z"
}

call.recording.uploaded

Key Value
callId Uniquely identifies the call.
fromUri URI of the caller.
toUri URI of the callee.
service Storage service where the recording lives (AWS S3 or Google Cloud Storage). Other payload "keys" will differ depending on this value.
bucket (AWS and gcloud)
key (AWS only)
destination (gcloud only)

Example:

1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
{
    "id": "68ca0l749f828dcfd2c408484a0fa270",
    "streamId": "1cd4606b-4c84-45b2-80f8-9318e7aea112",
    "subscriptionId": "ePXyDODmvAC77Vlv",
    "type": "call.recording.uploaded",
    "payload": {
        "callId": "8b41c365-11d8-1236-619d-5254002c49e7",
        "toUri": "sip:alice@foo.onsip.com",
        "fromUri": "sip:11238036862@jnctn.net",
        "service": "aws",
        "bucket": "example-bucket",
        "key": "recording.wav"
    },
    "createdAt": "2017-09-11T21:11:58.735641Z"
}

FAQs

Can an organization have more than one subscription?

Yes.

How is call direction determined?

Call direction can be determined by looking at the contents of the toUri and the fromUri. For example, if the toUri is a DID and the fromUri is a SIP address owned by your organization, then the call is "outbound" from your organization.

We are not receiving recording uploaded events. Why not?

You may need to set up Call Recording.

How do the toUri and the fromUri map to a user?

The URI fields contain SIP addresses. Each user on our platform is assigned a unique SIP address, and a user's information can be retrieved with the UserAddressRead action. Note that events are not limited to users either, events are generated for all SIP addresses.

Are the streamId and callId guaranteed to be GUIDs?

The streamId is generated on our platform and is guaranteed to be a GUID. The callId is pulled from the Call-ID SIP header field, which is created by the User Agent as a globally unique identifier for the dialog (source: RFC 3261).

Implementation Notes

  • We only generate events for calls that go over the PSTN (a streamId is generated when the call hits our PSTN server).
  • Due to the distributed nature of our system, we cannot guarantee that events arrive in a particular order. Depending on the nature of the application consuming the events, this may or may not matter. One approach is to build the application such that it “updates” a backing store with the latest event. An event with the appropriate stream ID can always update the application’s data associated with a stream, regardless of when it arrives. Another approach is to drop events which come in unexpected orders. In practice we do not expect this to occur often, and only for events which are separated by a millisecond scale timeframe. The takeaway is that we make no guarantees that an application that absolutely expects the state diagram pictured above will work for every call.

Issues

  • Subscriptions are not currently viewable on the Admin Portal. To see existing subscriptions, use the WebhookSubscriptionBrowse API action.

Topics: Developer Docs