Webhooks Guide
Overview
Webhooks are a system of automated notifications indicating that an event has occurred in the CallFire system. Rather than requiring you to pull information via our API, webhooks push information to your destination when important events occur. Resource notifications are delivered via HTTP POST to a destination endpoint on your server and are sent based on the events you choose.
List of resources and its events supporting webhooks:
- OutboundCall
- finished
- InboundCall
- finished
- OutboundText
- finished
- InboundText
- finished
- CallBroadcast
- started
- stopped
- finished
- TextBroadcast
- started
- stopped
- finished
- ContactList
- validationFinished
- validationFailed
- OutboundTextDeliveryReport
- Received
- MonthlyRenewal
- failed
- finished
- LowBalance
- failed
- finished
CallFire allows you to subscribe to multiple events on the same resource. After you create a listener, you can use a sample payload to simulate a webhook event to verify that your listener can successfully receive event data without any connectivity issues. After you verify your listener, subscribe it to a list of events. Finally, monitor the notifications that your listener receives when events occur.
How to Create Webhook
The following code samples show how to create webhooks in different programming languages. You can also use the curl utility for straight HTTP requests.
[[code-container]] [+curl] request:
#!/usr/bin/env bash
curl -u username:password -H "Content-Type:application/json" -X POST "https://api.callfire.com/v2/webhooks" -d '
{
"name":"Sms sent",
"resource":"OutboundText",
"events":["Finished"],
"callback":"https://callback-service.com/listener"
}'
response:
{
"id": 4321
}
[-curl]
[+java]
import com.callfire.api.client.CallfireClient;
import com.callfire.api.client.api.common.model.ResourceId;
import com.callfire.api.client.api.webhooks.model.ResourceType;
import com.callfire.api.client.api.webhooks.model.Webhook;
class ApiClientSample {
public static void main(String[] args) {
CallfireClient client = new CallfireClient("api login", "api password");
Webhook webhook = new Webhook();
webhook.setName("sms sent");
webhook.setResource(ResourceType.OUTBOUND_TEXT);
webhook.getEvents().add(ResourceType.ResourceEvent.FINISHED);
webhook.setCallback("https://callback-service.com/listener");
ResourceId id = client.webhooksApi().create(webhook);
}
}
[-java]
[+csharp]
using System.Collections.Generic;
using CallfireApiClient;
using CallfireApiClient.Api.Common.Model;
using CallfireApiClient.Api.Webhooks.Model;
public class ApiClientSample
{
public static void Main(string[] args)
{
var client = new CallfireClient("api_login", "api_password");
var webhook = new Webhook
{
Name = "sms sent",
Resource = ResourceType.OUTBOUND_TEXT,
Events = new HashSet<ResourceEvent> {ResourceEvent.FINISHED},
Callback = "https://callback-service.com/listener"
};
ResourceId resource = client.WebhooksApi.Create(webhook);
}
}
[-csharp]
[+js]
'strict'
const CallfireClient = require('callfire-api-client-js');
const client = new CallfireClient('api-login', 'api-password');
client.ready(() => {
client.webhooks.createWebhook({
body: {
name: 'sms sent',
resource: 'OutboundText',
events: ['Finished'],
callback: 'https://callback-service.com/listener'
}
})
.then((response) => {
console.log(response.obj);
})
.catch((err) => {
console.log('request error ' + err.data);
});
},
(clientError) => {
console.log('client error ' + clientError);
}
);
[-js]
[+python]
from callfire.client import CallfireClient
client = CallfireClient('api-login', 'api-password')
response = client.webhooks.createWebhook(
body={
'name': 'sms sent',
'resource': 'OutboundText',
'events': ['Finished'],
'callback': 'https://callback-service.com/listener'
}
).result()
# see sample JSON response for this API
# on 'curl' samples tab
print(response)
[-python]
[+php]
<?php
class ApiClientSample {
public static function main() {
$client =\CallFire\Api\DocumentedClient::createClient ("login", "password");
$request = $client -> createWebhook();
$body = '{
"name":"Sms sent",
"resource":"OutboundText",
"events":["Finished"],
"callback":"https://callback-service.com/listener"
}';
$request->getOperationConfig()->setBodyParameter($body);
$result = $client->request($request);
$json = json_decode($result->getBody());
}
}
ApiClientSample::main();
[-php]
[[/code-container]]
You can find a description of all webhook API operations, JSON models, and request parameters at Webhooks API page
Callback JSON payload samples
[[code-container]] [+call]
{
"timestamp":1469540830377,
"webhookId":3749003,
"webhookName":"Test Webhook",
"resourceType":"OutboundCall",
"events":[
{
"timestamp":1469540830377,
"webhookId":3749003,
"resourceType":"OutboundCall",
"eventType":"Finished",
"resource":{
"id":1024990109003,
"fromNumber":"12132212384",
"toNumber":"12132041238",
"state":"FINISHED",
"campaignId":13194908003,
"batchId":11305143003,
"contact":{
"id":537189974003,
"firstName":"testFirstName",
"lastName":"testLastName",
"zipcode":"90025",
"homePhone":"12132041238",
"workPhone":"12132041238"
},
"inbound":false,
"created":1469565220000,
"modified":1469565232000,
"labels":[
"Test Label 1",
"Test Label 2"
],
"finalCallResult":"AM",
"records":[
{
"id":571797202003,
"billedAmount":1,
"finishTime":1469565233000,
"result":"AM",
"originateTime":1469565225000,
"answerTime":1469565227000,
"duration":6
}
],
"agentCall":false
}
}
]
}
[-call]
[+text]
{
"timestamp":1469540830377,
"webhookId":3749003,
"webhookName":"Test Webhook",
"resourceType":"OutboundText",
"events":[
{
"timestamp":1469540830377,
"webhookId":3749003,
"resourceType":"OutboundText",
"eventType":"Finished",
"resource":{
"id":1023905054003,
"fromNumber":"12132041238",
"toNumber":"14246525473",
"state":"FINISHED",
"campaignId":9362663003,
"contact":{
"id":537189974003,
"firstName":"testFirstName",
"lastName":"testLastName",
"zipcode":"90025",
"homePhone":"12132041238",
"workPhone":"12132041238"
},
"inbound":false,
"created":1469540830000,
"modified":1469540828000,
"labels":[
"Test"
],
"message":"Test Message",
"finalTextResult":"RECEIVED",
"records":[
{
"id":570794145003,
"billedAmount":1.0,
"finishTime":1469540830000,
"message":"Test Message",
"textResult":"RECEIVED"
}
],
"media":[
]
}
}
]
}
[-text]
[+call-broadcast]
{
"timestamp":1469538171888,
"webhookId":3745003,
"webhookName":"Test Webhook",
"resourceType":"CallBroadcast",
"events":[
{
"timestamp":1469538171888,
"webhookId":3745003,
"resourceType":"CallBroadcast",
"eventType":"Finished",
"resource":{
"id":13175997003,
"name":"Test Webhook",
"status":"STOPPED",
"lastModified":1469538170000,
"fromNumber":"12132212384",
"localTimeRestriction":{
"enabled":true,
"beginHour":8,
"beginMinute":0,
"endHour":21,
"endMinute":0
},
"schedules":[
{
"id":1617524003,
"startDate":{
"year":2016,
"month":7,
"day":28
},
"startTimeOfDay":{
"hour":9,
"minute":0,
"second":0
},
"stopTimeOfDay":{
"hour":17,
"minute":0,
"second":0
},
"timeZone":"America/Los_Angeles",
"daysOfWeek":[
"MONDAY"
]
}
],
"retryConfig":{
"maxAttempts":1,
"minutesBetweenAttempts":60,
"retryResults":[
],
"retryPhoneTypes":[
"FIRST_NUMBER"
]
},
"maxActive":100,
"labels":[
"Label1"
],
"resumeNextDay":false,
"recipients":[
{
"contactId":1232412334
}
],
"sounds":{
"liveSoundText":"Test",
"liveSoundId":3363224003,
"machineSoundText":"Test",
"machineSoundId":3363224003,
"transferSoundText":"Test",
"transferSoundId":3363224003
},
"answeringMachineConfig":"LIVE_WITH_AMD",
"maxActiveTransfers":1
}
}
]
}
[-call-broadcast]
[+text-broadcast]
{
"timestamp":1469538745454,
"webhookId":3747003,
"webhookName":"Test Webhook",
"resourceType":"TextBroadcast",
"events":[
{
"timestamp":1469538745454,
"webhookId":3747003,
"resourceType":"TextBroadcast",
"eventType":"Finished",
"resource":{
"id":13163667003,
"name":"TEST Text Broadcast",
"status":"STOPPED",
"lastModified":1469538744000,
"fromNumber":"12132041238",
"localTimeRestriction":{
"enabled":true,
"beginHour":8,
"beginMinute":0,
"endHour":21,
"endMinute":0
},
"schedules":[
{
"id":1617524003,
"startDate":{
"year":2016,
"month":7,
"day":28
},
"startTimeOfDay":{
"hour":9,
"minute":0,
"second":0
},
"stopTimeOfDay":{
"hour":17,
"minute":0,
"second":0
},
"timeZone":"America/Los_Angeles",
"daysOfWeek":[
"MONDAY"
]
}
],
"retryConfig":{
"maxAttempts":1,
"minutesBetweenAttempts":60,
"retryResults":[
],
"retryPhoneTypes":[
"FIRST_NUMBER"
]
},
"maxActive":100,
"labels":[
"Label1"
],
"resumeNextDay":false,
"recipients":[
{
"message":"Test Message",
"contactId":1232412334
}
],
"message":"Test Message",
"bigMessageStrategy":"SEND_MULTIPLE",
"media":[
]
}
}
]
}
[-text-broadcast]
[+contact-list]
{
"timestamp":1496758301032,
"webhookId":3745004,
"webhookName":"testContactListValidationFinish",
"resourceType":"ContactList",
"events":[
{
"timestamp":1496758301032,
"webhookId":3745004,
"resourceType":"ContactList",
"eventType":"ValidationFinished",
"resource":{
"id":10,
"name":"API Contact List 2017-06-06 07:11:24",
"size":2,
"created":1496758285000,
"status":"ACTIVE"
}
}
]
}
[-contact-list]
[+text-delivery-report]
{
"timestamp": 1502221022807,
"webhookId": 1,
"webhookName": "ASA DLR Test",
"resourceType": "OutboundTextDeliveryReport",
"events": [
{
"timestamp": 1502221022807,
"webhookId": 1,
"resourceType": "OutboundTextDeliveryReport",
"eventType": "Received",
"resource": {
"id": 7420017300281076000,
"actionId": 123123123123,
"campaignId": 123456789,
"toNumber": "12135551234",
"fromNumber": "12138888888",
"timestamp": 1502221022571,
"deliveryState": "DELIVERED",
"deliveryCategory": "DELIVERED"
}
}
]
}
[-text-delivery-report]
[[/code-container]]
How to Authenticate Webhook Requests
CallFire webhooks have a secret token that is used as a signing key to the HmacSHA1 hash of JSON payload, which is returned in a 'X-CallFire-Signature' header. This header can be used to verify that the callback POST is coming from CallFire. You must provide a webhook.token when creating a webhook. You can change a webhook's secret key at any time. CallFire will immediately begin using the new key to sign requests.
Verifying request signature
[[code-container]] [+java]
import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class ApiRequestVerifier {
public String getHmacSignature(String data, String key) throws SignatureException {
String result;
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac;
try {
mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data.getBytes());
result = Base64.encodeBase64String(rawHmac).trim();
} catch (Exception e) {
throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
}
return result;
}
}
// then check signature in listener code
// String data = "{\"name\":\"test webhook\", \"callback\":\"sms:callfire\"}";
// String secret = "mysecrets";
// Assert.assertEquals("v14pF7d5C1+CHNIEWlg+sw9v0Xg=", new ApiRequestVerifier().getHmacSignature(data, secret));
[-java]
[+php]
<?php
function getHmacSignature($data, $secret) {
return base64_encode(hash_hmac('sha1', $data, $secret, true));
}
// then check signature in listener code
// $data = "{\"name\":\"test webhook\", \"callback\":\"sms:callfire\"}";
// $secret = "mysecrets";
// assert("v14pF7d5C1+CHNIEWlg+sw9v0Xg=" == getHmacSignature($data, $secret));
[-php]
[[/code-container]]
Read more about Callfire API security and authentication
The differences between API v1.1 Subscriptions and API v2 Webhooks
Despite the fact that Subscriptions and Webhooks are built to do the same job, at the same time both APIs are completely different, including the object model of their notifications. Below you can find a comparison table of both APIs:
Features | Subscriptions API | Webhooks API | Notes |
---|---|---|---|
Filtering by to/from number, broadcast ID, etc. | + | - | Currently webhooks do not support filtering, you have to do it on your side, otherwise you can use Subscription which supports filtering |
Multiple events per single instance | - | + | |
Different type of notification format (JSON, XML, EMAIL) | + | - | Subscriptions support choice of notification format e.g. JSON, XML or send notification via email |
Partial notification | - | + | Webhooks support partial notification response in order to increase API performance, read more on Partial Response page |
One-time notifications | - | + | Webhooks support through singleUse field |
Notification Signatures | - | + | CallFire can sign a notification with your secret key and let you validate an incoming request, the process described in previous paragraph. |
Which one to use ?
- if you are using only the API v1.1 we recommend you stay with Subscriptions API unless you have special requirements for security, e.g. validation of incoming requests.
- if you are using CallFire API v2 we recommend you use Webhooks API, if you want CallFire to filter notifications by different fields (toNumber, fromNumber, broadcastId, inbound/outbound, etc.). Or if you change the notification format for instance from JSON to XML, you should use Subscriptions API.
- CallFire allows usage of both APIs simultaneously, e.g set up an EMAIL notification via Subscriptions API and inbound call notification to callback URI through Webhooks API.