Webhooks

Webhooks

About Webhooks

Webhooks allow Kazoo to send HTTP requests to a third-party web server, alerting that server of events occurring within Kazoo. Typically, events would be fired for new calls, when a call is answered, and when a call is finished, though other events will be added in the future.

Schema

Web Hooks are subscriptions to allowed events that, when the event occurs, the event data is sent to the URI set in the Web Hook document.

KeyDescriptionTypeDefaultRequiredSupport Level
custom_dataThese properties will be added to the event and will overwrite existing values.object()false
enabledIs the webhook enabled and runningboolean()truefalse
formatWhat Body format to use when sending the webhook. only valid for ‘post’ & ‘put’ verbs`string(‘form-data’‘json’)`form-datafalse
hookThe trigger event for a request being made to ‘callback_uri’.string()truesupported
http_verbWhat HTTP method to use when contacting the server`string(‘get’‘post’‘put’)`post
include_internal_legsWhether to filter out call legs that are internal to the system (loopback)boolean()truefalse
include_subaccountsShould the webhook be fired for subaccount events.boolean()falsesupported
nameA friendly name for the webhookstring()truesupported
retriesRetry the request this many times (if it fails)integer(0..4)2falsesupported
security_settings.sha256_keySecret key to create a SHA256 HMAC with, so receiver can validate the webhook came from the clusterstring()false
security_settingsSecurity settings to harden webhook deliveryobject()false
uriThe 3rd party URI to call out to an eventstring(1..)truesupported
versionWhether to receive the full version of the event in the webhook payload`string(‘v1’‘v2’)`v1false

Fetch available webhooks on the system

Depending on the version of the KAZOO system running, the available webhooks may differ. Use this API to query the system for available webhooks.

GET /v2/webhooks

curl -v -X GET \
    -H "Content-Type:application/json" \
    -H "X-Auth-Token: {AUTH_TOKEN} \
    http://{SERVER}:8000/v2/webhooks
{
    "auth_token": "{AUTH_TOKEN}",
    "data": [
        {
            "description": "Receive notifications when sms is created",
            "id": "sms",
            "name": "SMS"
        },
        {
            "description": "Receive notifications when objects (like JSON document objects) in Kazoo are changed",
            "id": "object",
            "modifiers": {
                "action": {
                    "description": "A list of object actions to handle",
                    "items": [
                        "all",
                        "doc_created",
                        "doc_edited",
                        "doc_deleted"
                    ],
                    "type": "array"
                },
                "type": {
                    "description": "A list of object types to handle",
                    "items": [
                        "account",
                        "all",
                        "callflow",
                        "device",
                        "faxbox",
                        "media",
                        "user",
                        "vmbox",
                        "fax",
                        "mailbox_message",
                        "call_recording"
                    ],
                    "type": "array"
                }
            },
            "name": "Object"
        },
        {
            "description": "Fire a webhook when a notification event is triggered in Kazoo",
            "id": "notifications",
            "modifiers": {
                "type": {
                    "items": {
                        "account_zone_change": {
                            "description": "This event is triggered when an end user requests the home zone of an account is changed",
                            "friendly_name": "Account Zone Change"
                        },
                        "all": {
                            "description": "This event is triggered for any notification events",
                            "friendly_name": "All"
                        },
                        "bill_reminder": {
                            "description": "This event is triggered before a few days before the end of the month toremind account's owners of estimated service plan charges",
                            "friendly_name": "Bill Reminder"
                        },
                        "cf_notification": {
                            "description": "This event is triggered when an customer want send own notification, as example from callflow",
                            "friendly_name": "Customer defined notification"
                        },
                        "cnam_request": {
                            "description": "This event is triggered when an end user would like the CNAM for a number changed",
                            "friendly_name": "CNAM Update"
                        },
                        "customer_update": {
                            "description": "This event is triggered when the customer update API is used to deliver a message to the account",
                            "friendly_name": "Customer Update"
                        },
                        "denied_emergency_bridge": {
                            "description": "This event is triggered when a call to an number classified as emergency fails",
                            "friendly_name": "Emergency Call Failed"
                        },
                        "deregister": {
                            "description": "This event is triggered when a device fails to re-register and the contact expires",
                            "friendly_name": "De-Registration"
                        },
                        "first_occurrence": {
                            "description": "This event is triggered when an end user registers the first device and/or places the first call on an account",
                            "friendly_name": "Account First Occurrence"
                        },
                        "inbound_fax": {
                            "description": "This event is triggered when a fax is successfully received",
                            "friendly_name": "Successful Fax Reception"
                        },
                        "inbound_fax_error": {
                            "description": "This event is triggered when receiving a fax fails",
                            "friendly_name": "Fax Reception Error"
                        },
                        "low_balance": {
                            "description": "This event is triggered when an account is found with a balance below the notification threshold",
                            "friendly_name": "Account Low Balance"
                        },
                        "missed_call": {
                            "description": "This event is triggered when an corresponding missed call action in a callflow is invoked",
                            "friendly_name": "Missed Call"
                        },
                        "new_account": {
                            "description": "This event is triggered when an end user creates a new account",
                            "friendly_name": "New Account"
                        },
                        "new_user": {
                            "description": "This event is triggered when an end user creates a new user",
                            "friendly_name": "New User"
                        },
                        "outbound_fax": {
                            "description": "This event is triggered when a fax is successfully transmitted",
                            "friendly_name": "Successful Fax Transmission"
                        },
                        "outbound_fax_error": {
                            "description": "This event is triggered when transmitting a fax fails",
                            "friendly_name": "Fax Transmission Error"
                        },
                        "outbound_smtp_fax_error": {
                            "description": "This event is triggered when the received email-to-fax email is invalid",
                            "friendly_name": "Invalid Email-to-Fax Email"
                        },
                        "password_recovery": {
                            "description": "This event is triggered when an end user requests a password recovery link",
                            "friendly_name": "Password Recovery"
                        },
                        "port_cancel": {
                            "description": "This event is triggered when a port request is canceled",
                            "friendly_name": "Port Cancel"
                        },
                        "port_comment": {
                            "description": "This event is triggered when a comment is left on a port request",
                            "friendly_name": "Port Comment"
                        },
                        "port_pending": {
                            "description": "This event is triggered when a port request is accepted and submitted to a carrier",
                            "friendly_name": "Port Pending"
                        },
                        "port_rejected": {
                            "description": "This event is triggered when a port request is rejected",
                            "friendly_name": "Port Rejected"
                        },
                        "port_request": {
                            "description": "This event is triggered when a port is submitted for processing",
                            "friendly_name": "Port Request"
                        },
                        "port_scheduled": {
                            "description": "This event is triggered when a port is accepted by a carrier and scheduled",
                            "friendly_name": "Port Scheduled"
                        },
                        "port_unconfirmed": {
                            "description": "This event is triggered when a port is created, prior to submitting",
                            "friendly_name": "Port Unconfirmed"
                        },
                        "ported": {
                            "description": "This event is triggered when a port request for number is completed",
                            "friendly_name": "Ported"
                        },
                        "register": {
                            "description": "This event is triggered when a device registers but is not currently registered",
                            "friendly_name": "Registration"
                        },
                        "service_added": {
                            "description": "This event is triggered when an account's billable quantities change",
                            "friendly_name": "Service Added"
                        },
                        "topup": {
                            "description": "This event is triggered when an account automatic top-up is attempted",
                            "friendly_name": "Automatic Account Top-up"
                        },
                        "transaction": {
                            "description": "This event is triggered when a transaction is attempted",
                            "friendly_name": "Transaction Completed"
                        },
                        "voicemail_full": {
                            "description": "This event is triggered any time an attempt to leave a voicemail message is blocked because the voicemail box is full",
                            "friendly_name": "Voicemail Box Full"
                        },
                        "voicemail_new": {
                            "description": "This event is triggered any time a voicemail message is left",
                            "friendly_name": "New Voicemail Message"
                        },
                        "voicemail_saved": {
                            "description": "This event is triggered any time a voicemail message is saved in the voicemail box 'new' folder",
                            "friendly_name": "Voicemail Message Saved"
                        },
                        "webhook": {
                            "description": "This event is triggered when a corresponding webhook action in a callflow is reached",
                            "friendly_name": "Callflow Webhook Triggered"
                        }
                    },
                    "type": "object"
                }
            },
            "name": "Notifications Webhook"
        },
        {
            "description": "This webhook is triggered when a channel is destroyed, usually as a result of a hangup",
            "id": "channel_destroy",
            "name": "Channel Destroy"
        },
        {
            "description": "This webhook is triggered when a new channel is created",
            "id": "channel_create",
            "name": "Channel Create"
        },
        {
            "description": "This webhook is triggered when two channels are bridged together, such as two users/devices connected together",
            "id": "channel_bridge",
            "name": "Channel Bridge"
        },
        {
            "description": "This webhook is triggered when a channel establishes two-way audio, such as a voicemail box or the called party answering",
            "id": "channel_answer",
            "name": "Channel Answer"
        },
        {
            "description": "Events when calls get parked/retrieved",
            "id": "parking",
            "name": "Call Parking"
        }
    ],
   "page_size": 8,
   "request_id": "{REQUEST_ID}",
   "revision": "{REVISION}",
   "status": "success"
}

Get sample payloads of all webhook events

GET /v2/webhooks/samples

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/webhooks/samples

Example

Request:

curl -H 'Content-Type: application/json' 'http://{SERVER}:8000/v2/webhooks/samples'

Response:

{
  "data": [
    "webhooks_channel_answer",
    "webhooks_channel_bridge",
    "webhooks_channel_create",
    "webhooks_channel_destroy",
    "webhooks_notifications",
    "webhooks_object",
    "webhooks_parking"
  ],
  "revision": "{REVISION}",
  "timestamp": "{TIMESTAMP}",
  "version": "{VERSION}",
  "node": "{NODE}",
  "request_id": "{REQUEST_ID}",
  "status": "success"
}

Get sample payloads of a webhook event

GET /v2/webhooks/samples/{SAMPLE_ID}

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/webhooks/samples/{SAMPLE_ID}

You can use regular Crossbar query string filters to narrow down the samples, for example:

curl -H 'Content-Type: application/json' 'http://{SERVER}:8000/v2/webhooks/samples/webhook_notifications?filter_event_name=missed_call'
curl -H 'Content-Type: application/json' 'http://{SERVER}:8000/v2/webhooks/samples/webhook_object?filter_action=doc_created'

Example

Request:

curl -s -H 'Content-Type: application/json' 'http://{SERVER}:8000/v2/webhooks/samples/webhooks_parking'

Response:

{
  "page_size": 1,
  "data": [
    {
      "account_id": "5a2d994fbae69b1d6b01eb9f0e7dfe62",
      "call_id": "OWU4NzEwOTgyZWNiMjM0MzI0NjRkZDc4MWVmMjEyOWI",
      "callee_id_name": "Test Name",
      "callee_id_number": "5355543456",
      "caller_id_Number": "+15555432345",
      "caller_id_name": "Superman",
      "event_name": "PARK_PARKED",
      "parking_slot": 1
    }
  ],
  "revision": "{REVISION}",
  "timestamp": "{TIMESTAMP}",
  "version": "{VERSION}",
  "node": "{NODE}",
  "request_id": "{REQUEST_ID}",
  "status": "success"
}

List webhooks

GET /v2/accounts/{ACCOUNT_ID}/webhooks

Any webhooks with disable_reason in the summary has been auto-disabled.

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks

Create webhook

PUT /v2/accounts/{ACCOUNT_ID}/webhooks

curl -v -X PUT \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{"data": {
        "name": "New Calls",
        "uri": "http://my.{SERVER}/calls/new.php",
        "http_verb": "post",
        "hook": "channel_create",
        "retries":3
    }}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks

Get details of the webhook

GET /v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

Edit webhook

POST /v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

curl -v -X POST \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{"data": {
        "name": "New Calls",
        "uri": "http://my.{SERVER}/calls/new_calls.php",
        "http_verb": "post",
        "hook": "channel_create",
        "retries": 3
    }}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

Patch webhook

PATCH /v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

You can also patch an existing webhook:

curl -v -X PATCH \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{"data":{"enabled":true}}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

Delete a webhook

DELETE /v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

curl -v -X DELETE \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}

List Webhook Attempts

Webhooks tracks attempts to send the hook payloads to your URIs. You can get a listing of the more recent attempts to help debug what went wrong.

!!! note By default clusters are not configured to store successful webhook attempts.

GET /v2/accounts/{ACCOUNT_ID}/webhooks/attempts

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks/attempts
{
    "auth_token": "{AUTH_TOKEN}",
    "data": [
        {
            "client_error": "nxdomain",
            "hook_id": "{HOOK_ID}",
            "reason": "kazoo http client error",
            "result": "failure",
            "retries left": 2,
            "timestamp": 63590996563
        },
        {
            "hook_id": "{HOOK_ID}",
            "result": "success",
            "timestamp": 63590996562
        }
    ],
    "page_size": 2,
    "request_id": "{REQUEST_ID}",
    "status": "success"
    }

List attempts for a specific attempt

GET /v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}/attempts

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks/{WEBHOOK_ID}/attempts

Re-enable auto-disabled hooks in bulk

Webhooks will auto-disable failing hooks (if Kazoo can’t reach your server, or you take too long to respond with 200 OK, for instance). Especially if you’re a reseller with webhooks in your client accounts, it can be tedious to have to iterate through all your accounts and re-enable each hook. Fortunately, you can perform this bulk-enable action against an account or an account and its descendants.

Enable an account’s hooks

PATCH /v2/accounts/{ACCOUNT_ID}/webhooks

curl -v -X PATCH \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -d '{"data":{"re-enable":true}}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/webhooks

Enable an account’s and descendant accounts’ hooks

PATCH /v2/accounts/{ACCOUNT_ID}/descendants/webhooks

curl -v -X PATCH \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -d '{"data":{"re-enable":true}}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/descendants/webhooks

Hook Payload

Here’s what you can expect to receive when a webhook fires to your server:

Versioned payloads

When version is not set on the webhook configuration, the v1 format of the webhook event is used (see below).

When "version" = "v2", the shape of the event sent to the HTTP URL will resemble that of the websocket-fired event, a more verbose version of the event.

Base Payload (version 1)

  • hook_event: The type of hook being fired
  • call_direction: “inbound” or “outbound”, relative to Kazoo
  • timestamp: Gregorian timestamp of the event
  • account_id: ID of the account generating the event
  • request: SIP Request
  • to: SIP To
  • from: SIP From
  • call_id: SIP Call ID
  • other_leg_call_id: If bridged, the Call ID of the other leg
  • caller_id_name: Caller ID Name
  • caller_id_number: Caller ID Number
  • callee_id_name: Callee Name
  • callee_id_number: Callee Number

Most of these fields should be present on all payloads.

Hook Specific fields

  • channel_create
    • hook_event: channel_create
  • channel_answer
    • hook_event: channel_answer
  • channel_destroy
    • hook_event: channel_destroy
    • hangup_cause: SIP Hangup Cause (NORMAL_CLEARING, ORIGINATOR_CANCEL, etc)
    • hangup_code: SIP Hangup Code (404, 503, etc)
  • object
    • hook_event: object
    • action: doc_created, doc_updated, doc_deleted
    • type: user, vmbox, callflow, account, device, faxbox, media

Base payload (version 2)

See the blackhole documentation for more, but the general shape of the data will be a JSON envelope:

{"action":"event"
 ,"name":"{HOOK_EVENT}"
 ,"data":{HOOK_DATA}
}

Where {HOOK_DATA} will be the JSON object with the data of the event.

Hook Specific Custom Data

To restrict the kind of document or the action or both. You can set the custom data to:

{"name":"User edited webhook"
 ,"hook":"object"
 ,"custom_data":{
    "type": "user",
    "action": "doc_edited"
 }
 ,"uri":"https://..."
}

Both type and action will accept "all" to avoid needing to create a hook per action/type combinations.

Hook Security

For hooks using the “version 2” payload format, it is possible to include more information on the webhook payload to ensure the webhook event arrives from the cluster, timestamps to prevent replay attacks, etc.

!!! warning Please ensure the webhook also sets "format":"json" as “form-data” (the default) will result in integers (like timestamps) being encoded as strings, which will render the HMAC less useful.

Timestamps will be included on all payloads:

{"action":"{ACTION}"
 ,"name":"{HOOK_NAME}"
 ,"data":{...}
 ,"timestamp":{GREGORIAN_SECONDS}
}

If security_settings.sha256_key is included, an HMAC hash (base-64 encoded) of hmac_sha256(SHA256Key, "Timestamp" + "DataJSON") will be calculated and added to the payload:

SHA256Key = "123",
TimestampString = "63834206532",
DataJSON = "{\"foo\":\"bar\"}",

HMAC = base64_encode(hmac(SHA256Key, TimestampString + DataJSON)),

"z481V/jZHAnFEjNCMORc2FgX03CyzxV+fLV6BWhnY6U=" == HMAC
{"action":"{ACTION}"
 ,"name":"{HOOK_NAME}"
 ,"data":{"foo":"bar"}
 ,"timestamp":63834206532
 ,"hmac":"z481V/jZHAnFEjNCMORc2FgX03CyzxV+fLV6BWhnY6U="
}