Reporting Webhooks

Updated

Reporting Webhooks send real-time message activity events (e.g. sends, opens, clicks) as JSON in an HTTP POST. They’re useful in many cases, including analyzing message activity outside of Customer.io.

You can find information about each individual event type on our Reporting Webhooks API documentation.

 Looking to send a custom webhook to a specific API via your workflow?

Check out Webhook Actions, instead.

Setup

  1. Log in and go to Data & Integrations > Integrations.

  2. Find and select Reporting Webhooks.

  3. Click Add Reporting Webhook.

  4. Enter the Webhook Endpoint URL where you want to receive events. While the endpoint URL can be either HTTP or HTTPs, we recommend HTTPS to protect customer information.

  5. Select the events you want to receive.

  6. (Optional) Select the Send Frequency and Body Content options.

    • Send Frequency: This determines whether you receive events the first time they happen or every time they happen.
    • Body Content: Enable this to include message body content (and email headers) in all of the “Sent” events we send to you.
  7. Choose Save and Enable Webhook at the bottom right of the page.

reporting_webhook_configuration
reporting_webhook_configuration

(Optional) Allowlist our IP addresses

If you have firewalls or rules in place that only allow connections from specific IPs, add Customer.io IP addresses to your allowlist so your systems can receive connections from us.

US RegionEU Region
35.188.196.18334.76.143.229
104.198.177.21934.78.91.47
104.154.232.8734.77.94.252
130.211.229.19535.187.188.242
104.198.221.2434.78.122.90
104.197.27.1535.195.137.235
35.194.9.154130.211.108.156
104.154.144.51104.199.50.18
104.197.210.1234.78.44.80
35.225.6.7335.205.31.154

Disabling webhooks

If you want to stop sending webhook events, you can disable the webhook.

  1. Go to Data & Integrations > Integrations.
  2. Go to Reporting Webhooks.
  3. Select your webhook and click Disable.
Disable Toggle
Disable Toggle

You can also edit your webhook, change the state to disabled, and click Save.

Disable Toggle 2
Disable Toggle 2

Test a webhook

To inspect Webhook Events before pointing them at your own servers, use a service like webhook.site.

 Warning

Enabling a webhook endpoint can cause you to send sensitive data to an external recipient. We recommend creating a test/sandbox workspace to test your webhooks so that you don’t inadvertently leak sensitive data to a potentially unsecured endpoint.

To send a test event, press Send Test while editing your Webhook Endpoint.

image.png
image.png

Events

The following events are available via webhook:

To only receive specific events, refine your selections within the nested list:

Webhook Events
Webhook Events

If you have a specific request for an event not listed here that you would like to be notified of, please contact us.

Webhook attributes

AttributeDescription
action_idIf the delivery was created as part of a Campaign or API Triggered Broadcast workflow, this is the ID for the unique workflow item that caused the delivery to be created. It can be used to retrieve full message details, including content, via the Campaign endpoint of our API.
broadcast_idIf applicable, the ID of the API Triggered Broadcast that generated the message. It can be used to retrieve message details, including the actions in your broadcast, via the Braodcast endpoint of our API.
campaign_idIf applicable, the ID of the Event-triggered, Segment-triggered, or Date-triggered Campaign that generated the message.
contentThe body content of a sent message; if a message is an email, body content also contains headers. This can be useful if you want to display message content in your app. You must opt in to receiving content.
content_idIf the message was part of a newsletter split test, this is the ID of the split test variation.
customer_idThe ID of the person the webhook event represents. In a workspace supporting both email and id as identifiers, this value can be null. The value is empty if the person has been deleted. This field is generally considered deprecated. You should ignore this value and rely on the identifiers object.
identifiersContains identifiers for the person the webhook event is associated with. The object is empty if a person was deleted. If your workspace supports both email and ID as identifiers, this object contains id, email, and cio_id, and both id and email can be null. If your workspace only supports ID, this object only contains id.
delivery_idThe unique ID of the delivery record associated with the message.
device_idOnly on push-related events, the ID of the associated mobile device.
device_platformOnly on push-related events, the platform of the associated mobile device.
email_addressOnly on customer_subscribed and customer_unsubscribed events, this is the email address of the person.
event_idThe unique ID of the reporting webhook event being sent. This can be useful for deduplicating purposes.
event_typeThe type of event sent (e.g. email_sent, sms_drafted).
hrefOnly on “clicked” events, the fully rendered URL of the link that was clicked.
journey_idThe ID for the path a person went through in a Campaign or API Triggered Broadcast workflow. In our Data Warehouse Sync, this is referred to as subject_id.
link_idOnly on “clicked” events, the ID of the tracked link that was clicked.
newsletter_idIf applicable, the ID of the Newsletter that generated the message. It can be used to retrieve full message details via the Newsletters endpoint of our API.
recipientThe address of the message recipient. This could be an email address, a phone number, a mobile device ID, a Webhook URL, or a Slack username or channel.
subjectFor email events, this is the subject of the email.
failure_messageIf applicable, the reason a message failed to send.
timestampThe timestamp at which the event being reported took place.
transactional_message_idIf a message is transactional, this is the unique identifier of the transactional message “template” that you sent (and referenced in the transactional message payload). If you send transactional messages without referencing transactional_message_id (by passing body, subject, and from values at send time), this value is 1.
tracked_responseOnly available for in-app message “clicked” events, the tracked response value if Track Clicks is enabled for the in-app message action/component that a person clicked/tapped.
trigger_event_idThe id of the event that triggered an event-triggered campaign (not an API-triggered broadcast)

The identifiers object

Webhook payloads include an identifiers object. This object contains the unique identifiers for the person your event represents.

The items that the identifiers object can contain are defined by your workspace settings. If your workspace supports both email and id as identifiers, the identifiers block contains both (though either can be null if not set) and a cio_id—a unique, immutable identifier set by Customer.io to identify people canonically across changes to their other identifiers.

If your workspace uses ID as its only identifier, the identifiers object only contains the id.

{
  "data": {
    "action_id": 36,
    "broadcast_id": 9,
    "customer_id": "abcd-1234",
    "identifiers": {
        "cio_id": "03000001",
        "id": "abcd-1234",
        "email": "test@example.com"
    },
    "delivery_id": "RPILAgABcRhIBqSp7kiPekGBIeVh",
    "recipient": "test@example.com",
    "subject": "hello"
  },
  "event_id": "01E4C8AY5K21N2QNRBD9YXJ13Z",
  "object_type": "email",
  "metric": "sent",
  "timestamp": 1585254331
}

Format

 Go to our new webhook documentation for examples of each event type.

Customer.io Webhooks are HTTP POST requests encoded in JSON. The requests have a User Agent header containing “Customer.io Web Hooks x.x” where “x.x” is the version number.

The JSON body contains a general top-level section included in all webhook requests, as well as a “data” attribute, which contains data specific to the type of event.

Below is an example of an HTTP request for an email-related event:

User-Agent: Customer.io Web Hooks 1.0
Host: webhook.site
Content-Type: application/json
Accept-Encoding: gzip
Cf-Connecting-Ip: 167.114.157.9
X-Request-Id: 	7e6f46cd-480e-4354-93ce-74b770015c7f
Connect-Time: 1
Content-Length: 1100
Cf-Visitor: {"scheme":"http"}
Total-Route-Time: 0
Cf-Ipcountry: CA
Cf-Ray: 2b62237a83a12507-ORD
Connection: close
Via: 1.1 vegur

RAW BODY

{
  "data": {
    "action_id": 36,
    "broadcast_id": 9,
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
        "email": "test@example.com",
        "cio_id": "d9c106000001"
    },
    "delivery_id": "RPILAgABcRhIBqSp7kiPekGBIeVh",
    "recipient": "test@example.com",
    "subject": "hello"
  },
  "event_id": "01E4C8AY5K21N2QNRBD9YXJ13Z",
  "object_type": "email",
  "metric": "clicked",
  "timestamp": 1585254331
}

Webhook Sent events include a X-CIO-Delivery-ID header. This is a delivery ID that uniquely identify each event.

Timeouts and failures

We have a 4 second timeout for calls to your webhook endpoint. If we don’t get a successful (2xx) response during those 4 seconds, we retry the webhook over a period of seven days with an exponential backoff. We backlog subsequent webhook calls and won’t continue processing them until the timed-out webhook call returns a successful response or the seven day retry period expires.

If your webhook server responds with any of the following status codes, we’ll wait for one hour before we retry the failed call. As with timeouts, we use the same exponential backoff for retries and we’ll backlog subsequent webhook calls until the failed call returns a successful response or the seven day retry period expires.

  • Error codes: 400, 401, 402, 403, 404, 405, 410, 429, 500, 502, 521.
  • Error responses: EOF, server misbehaving, connect: connection refused, read: connection reset by peer, tls: failed to verify certificate: x509:

If you have issues with your webhook server and you want to temporarily block our servers, you can look up the current set of IP addresses we use via this API endpoint.

Securely verify requests

For security purposes, webhooks are delivered with an X-CIO-Signature header. This signature is generated by combining your webhook signing key with the body of webhook request using a standard HMAC-SHA256 hash.

For reporting webhooks, you can find the signing key on the same page you enter your webhook endpoint: Data & Integrations > Integrations > Reporting webhooks. For webhook actions, you can find the signing key in Settings > Workspace Settings > API & Webhook Credentials under the Webhook Signing Key tab.

To validate a signed request you’ll first need to retrieve the X-CIO-Timestamp header sent with the webhook request and the body of the request. Combine the version number, timestamp, and body—delimited by colons—to form a string like v0:<timestamp>:<body> (the version number is always v0). Using HMAC SHA256, hash the string using your webhook signing secret as the hash key. Compare this value to the value of the X-CIO-Signature header sent with the request to confirm that the request originated with Customer.io.

 Important note:

Always use the request’s raw body when constructing the hash. Do not use any transformations such as JSON.stringify() (Node.js) or json.dumps() (Python) as there are subtle differences between parsing libraries.

Here’s an example of a validation function in Golang.

import (
	"encoding/hex"
	"crypto/hmac"
	"crypto/sha256"
	"strconv"
	"fmt"
)

func CheckSignature(WebhookSigningSecret, XCIOSignature string, XCIOTimestamp int, RequestBody []byte) (bool, error) {
  signature, err := hex.DecodeString(XCIOSignature)
  if err != nil {
    return false, err
  }

  mac := hmac.New(sha256.New, []byte(WebhookSigningSecret))

  if _, err := mac.Write([]byte("v0:" + strconv.Itoa(XCIOTimestamp) + ":")); err != nil {
    return false, err
  }
  if _, err := mac.Write(RequestBody); err != nil {
    return false, err
  }

  computed := mac.Sum(nil)

  if !hmac.Equal(computed, signature) {
    fmt.Println("Signature didn't match")
    return false, nil
  }

  fmt.Println("Signature matched!")
  return true, nil
}

Frequently asked questions

  1. Can webhooks contain the message body? Yes! We can send the message body for all channels, but you must opt in to this option during setup.
  2. How can I secure webhooks? It’s possible to add basic authentication in the Webhook Endpoint URL field (e.g. http://username:password@example.com).
  3. How do you identify each message that is going out? Each message sent from Customer.io has a delivery_id unique identifier that is also part of the default unsubscribe link: https://track.customer.io/unsubscribe/MjYyMTI6Fs_YAmQAAnMAFeEaAU2YoV7tFRoYVh6HYAFzOjIyOTkwMQA=

To view a particular message in the UI, select Deliveries & Drafts from the left panel, click under the “Action” column for any message in the list, then replace the end of the URL in your browser with the delivery_id you’re interested in. For example:

https://fly.customer.io/env/26212/outbox/deliveries/MjYyMTI6Fs_YAmQAAnMAFeEaAU2YoV7tFRoYVh6HYAFzOjIyOTkwMQA=

The delivery_id is also displayed under the Metadata details in the right-hand column of the page.

  1. Can I specify which campaign(s) get forwarded to an external webhook? No. If you need to monitor only a specific campaign, however, it’s possible to handle the logic on your end to filter out unwanted webhook events based on campaign_id.
  2. Can I get a webhook when a customer gets added to a segment? No. But if you want to pull a list of people in a given segment, you can do so using the /segments/:id/membership endpoint of our API. Alternatively, you could create a segment-triggered campaign based on the segment you’re interested in, set the email inside to “Queue Draft” and then monitor the email_drafted events for that campaign_id.
  3. Do you send an event for each click performed by a user? By default, only the first click event is sent. If you wish to have each click recorded, you can set the Send Frequency on your Webhook Endpoint accordingly:
    Webhook Send Frequency
    Webhook Send Frequency
  4. Can Reporting Webhooks be rate limited? By default, we only send one event per action (sent, opened, clicked, etc.) to limit the output. No further rate limiting is available.
  5. Is it possible to host a webhook endpoint in Customer.io? No. For incoming data, you’ll need to use our REST API or our Segment integration.

Send third-party delivery metrics to Customer.io

If you use outgoing webhooks to trigger messages from a third-party provider that is not natively integrated with Customer.io, you can send incoming calls to our Track metrics API to capture metrics in Customer.io. This allows you to see delivery metrics for all of your messages in one place.

The following steps assume you already have at least one campaign that uses a Send and Receive Data webhook action to send messages via a third party’s API. To send delivery metrics back to Customer.io:

  1. Create a reporting webhook in Integrations where you pass webhook events.
    1. Send the reporting webhook to a URL from your third-party service provider. The third-party provider can reference the X-CIO-Delivery-ID in the header of the message to uniquely identify that message.
  2. From the third-party provider, you’ll send updates back via our Track API including delivery metrics (“delivered”, “bounced”, etc.).
    1. You can view the metrics anywhere you can see them within your Customer.io account, such as Campaign Metrics, as well as data-out integrations. Note that the metrics will show as Webhook messages, since that was the type of delivery from your campaign.

Webhook event examples

We’ve moved webhook examples, including details about the schema for each individual webhook event, to our API documentation.

Customer event examples

{
  "data": {
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
    },
    "email_address": "test@example.com"
  },
  "event_id": "01E4C4CT6YDC7Y5M7FE1GWWPQJ", //the id of the reporting webhook instance
  "object_type": "customer",
  "metric": "subscribed",
  "timestamp": 1585250199
}

Email event examples

{
  "data": {
    "action_id": 36,
    "campaign_id": 9,
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
    },
    "delivery_id": "RPILAgABcRhIBqSp7kiPekGBIeVh",
    "trigger_event_id": "21E4C3CT6YDC7Y4N7FE1GWWABC" //the id of the event that triggered an event-triggered campaign (not an API-triggered broadcast)
  },
  "event_id": "01E4C4G1S0AMNG0XVF2M7RPH5S", //the id of the reporting webhook instance
  "object_type": "email",
  "metric": "drafted",
  "timestamp": 1585250305
}

Push notification event examples

{
  "data": {
    "action_id": 37,
    "campaign_id": 9,
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
    },
    "delivery_id": "RPILAgUBcRhIBqSfeiIwdIYJKxTY",
    "trigger_event_id": "21E4C3CT6YDC7Y4N7FE1GWWABC" //the id of the event that triggered an event-triggered campaign (not an API-triggered broadcast)
  },
  "event_id": "01E4C4G1S0HZ7C4220T6QNY8JX", //the id of the reporting webhook instance
  "object_type": "push",
  "metric": "drafted",
  "timestamp": 1585250305
}

In-App message event examples

{
  "event_id": "01E4C4CT6YDC7Y5M7FE1GWWPQJ", //the id of the reporting webhook instance
  "object_type": "in-app",
  "timestamp": 1613063089,
  "metric": "drafted",
  "data": {
    "trigger_id": 1,
    "customer_id": "42",
    "delivery_id": "ZAIAAVTJVG0QcCok0-0ZKj6yiQ==",
    "action_id": 96,
    "broadcast_id": 2,
    "journey_id": "01GW20GXAAXBKZD8J96M8FNV3R",
    "parent_action_id": 1,
    "identifiers": {
      "id": "42",
      "email": "test@example.com",
      "cio_id": "d9c106000001"
    }
  }
}

SMS event examples

{
  "data": {
    "action_id": 38,
    "broadcast_id": 9,
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
    },
    "delivery_id": "RPILAgIBcRhIBqSZNqzgZVFoivwW"
  },
  "event_id": "01E4C4G1S02P8D0G2JMY88KAFN", //the id of the reporting webhook instance
  "object_type": "sms",
  "metric": "drafted",
  "timestamp": 1585250305
}

Slack event examples

{
  "data": {
    "action_id": 39,
    "campaign_id": 9,
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
    },
    "delivery_id": "RPILAgQBcRhIBqRiZAc0fyQiLvkC",
    "trigger_event_id": "21E4C3CT6YDC7Y4N7FE1GWWABC" //the id of the event that triggered an event-triggered campaign (not an API-triggered broadcast)
  },
  "event_id": "01E4C4G1S0T3Y4V8W7F6MNFA8S", //the id of the reporting webhook instance
  "object_type": "slack",
  "metric": "drafted",
  "timestamp": 1585250305
}

Webhook example events

{
  "data": {
    "action_id": 40,
    "campaign_id": 9,
    "customer_id": "0200102",
    "identifiers": {
        "id": "0200102",
    },
    "delivery_id": "RPILAgEBcRhIBqSrYcXDr2ks6Pj9",
    "trigger_event_id": "21E4C3CT6YDC7Y4N7FE1GWWABC" //the id of the event that triggered an event-triggered campaign (not an API-triggered broadcast)
  },
  "event_id": "01E4C4G1S04QCV1NASF4NWMQNR", //the id of the reporting webhook instance
  "object_type": "webhook",
  "metric": "drafted",
  "timestamp": 1585250305
}

Legacy email webhook format

On April 8, 2020, we streamlined our email webhook payloads, removing unneeded data in order to improve our processing speed and reliability.

If you had Reporting Webhooks enabled before April 8, 2020, the old email webhook payload remains unchanged.

The example below covers any of the email-related activity:

User-Agent: Customer.io Web Hooks 1.0
Host: webhook.site
Content-Type: application/json
Accept-Encoding: gzip
Cf-Connecting-Ip: 167.114.157.9
X-Request-Id: 	7e6f46cd-480e-4354-93ce-74b770015c7f
Connect-Time: 1
Content-Length: 1100
Cf-Visitor: {"scheme":"http"}
Total-Route-Time: 0
Cf-Ipcountry: CA
Cf-Ray: 2b62237a83a12507-ORD
Connection: close
Via: 1.1 vegur

RAW BODY

{
"data": {
  "campaign_id": "1000002",
  "campaign_name": "Upgrade to Premium",
  "customer_id": "98513",
  "email_address": "customer@example.com",
  "email_id": "NTE4MzE6FwGLxwJkAAJkABcBIfcaAVVvdGukFUsYV2hY6QFlOjQ4YTZhODljLTM3MjktMTFlNi04MDQwLTYzNGY3NzAzM2NhNjozNDMwMzEA",
  "message_id": "1000013",
  "message_name": "First Upgrade Email",
  "subject": "Have any doubts?",
  "template_id": "343031",
  "variables": {
    "attachments": null,
    "customer": {
      "created_at": 1466453747,
      "email": "customer@example.com",
      "id": 98513,
      "name": "John Doe",
      "plan_name": "free"
    },
    "email_id": "NTE4MzE6FwGLxwJkAAJkABcBIfcaAVVvdGukFUsYV2hY6QFlOjQ4YTZhODljLTM3MjktMTFlNi04MDQwLTYzNGY3NzAzM2NhNjozNDMwMzEA",
    "event": {
      "page": "https://customer.io/pricing/"
    },
    "event_id": "48a6a89c-3729-11e6-8040-634f77033ca6",
    "event_name": "viewed_pricing_page",
    "from_address": null,
    "recipient": null,
    "reply_to": null
  }
},
"event_id": "b50cb221c60f87cdf06e",
"event_type": "email_drafted",
"timestamp": 1466456299
}

Legacy email webhook attributes

  • campaign_id and campaign_name: refer to the transactional message, segment-triggered campaign or newsletter that generated the email

  • customer_id: user id (can be retrieved from the person profile). Only present if the person is still active (not included if the person has been deleted).

  • email_address: “To” email address

  • email_id: unique message id (each individual message sent from Customer.io has a different “email_id”); can also be found in the unsubscribe link URL

  • event: specific to event-triggered campaigns; includes all the event attributes

  • event_id (data section): specific to event-triggered campaigns; id of the event that generated the message (not visible in the UI)

  • event_id: internal attribute; id associated with the email_type action

  • event_name: specific to event-triggered campaigns; name of the event that powers the campaign

  • event_type: type of event (“email_drafted”, “email_sent”, etc.)

  • from_address: from_address set via the event

  • href and link_id: specific to “email_clicked” events

  • href: first URL clicked by the user

  • link_id: internal attribute (not visible in the UI)

  • message_id: campaign email id; can be found in the campaign URL after emails/ (e.g. https://fly.customer.io/env/51831/v2/composer/emails/225039)

  • message_name: the name of the campaign email

  • reason: specific to the “email_bounced” and “email_dropped” events, mentions the cause of the bounce/suppression (e.g.: Invalid)

  • recipient: email address of a user that does not exist inside Customer.io

  • reply_to: reply_to address set via the event

  • subject: email subject

  • template_id: internal attribute, each email inside a campaign can have multiple template ids depending on the changes made over time. You can view it in the UI by filtering for a specific email under Email Log. For example: https://fly.customer.io/env/51831/email_logs?campaign=139744&template=343216

  • timestamp: date and time when the event took place in unix (seconds since epoch) format

  • variables:

    • attachments: specific to transactional emails with small attachments (e.g. .ics files)
    • customer: all the attributes associated with your user
Copied to clipboard!
  Contents
Is this page helpful?