# Responding to Platform Events using Webhooks

The Outreach API normally requires *your* action to retrieve, create or update resources. But with **Webhooks**, the
Outreach API will automatically notify you whenever events that you are interested in occur. You can create webhooks
just like any other resource. You must provide an HTTPS URL, and you can optionally specify the resources and actions
that this webhook will respond to. By default, those values will be an asterisk `*`, which indicates that all applicable
resources and actions will be delivered. Possible values include:

| Resource | Actions |
|  --- | --- |
| * | * created updated destroyed |
| account | * created updated destroyed |
| call | * created updated destroyed |
| emailAddress | * created updated destroyed |
| import | * created finished |
| kaiaRecording | * created |
| mailing | * created updated destroyed bounced delivered opened replied |
| opportunity | * created updated destroyed |
| opportunityProspectRole | * created updated destroyed |
| prospect | * created updated destroyed |
| sequence | * created updated destroyed |
| sequenceState | * created updated destroyed advanced finished |
| task | * created updated destroyed completed |
| user | * created updated |


When an active webhook sees a change to an applicable resource-action combination, the Outreach API will POST a
JSON-API-styled request to the webhook's URL. Please note that create and update actions will contain only
created/changed attributes, while deleted actions will contain the last known set of attributes and relationships before
the resource was deleted.

## Webhooks creation

For example, to create a webhook that will trigger after each "task completed" event:

p
Request

```
POST https://api.outreach.io/api/v2/webhooks
```


```json5
{
  data: {
    type: "webhook",
    attributes: {
      action: "completed",
      resource: "task",
      url: "https://foo.bar/webhooks",
    },
  },
}
```

p
Response

```json5
{
  data: {
    type: "webhook",
    id: 1,
    attributes: {
      action: "completed",
      active: true,
      cleanupToken: "eyJh....",
      createdAt: "2025-10-21T22:54:57.000Z",
      creatorAppId: "<client id>",
      creatorAppName: "<integration name>",
      disabledReason: null,
      disabledSince: null,
      disabledUntil: null,
      payloadVersion: 1,
      resource: "task",
      secret: "abcd1234",
      updatedAt: "2025-10-21T22:54:57.000Z",
      url: "https://foo.bar/webhooks",
    },
  },
}
```

## Payload sent

When a task-completed event occurs, a JSON-API-styled request will be sent to the webhook's URL that includes the
changed attributes and meta information about the webhook event.

The data payload of the webhook request comes in two different versions. The **default** is version 1:


```
POST https://foo.bar/webhooks
```


```json5
{
  data: {
    type: "task",
    id: 1,
    attributes: {
      completed: true,
      completedAt: "2017-01-01T00:00:00",
      state: "completed",
    }
  },
  meta: {
    deliveredAt: "2017-01-01T00:00:01",
    eventName: "task.completed",
  }
}
```

Version 2 of the webhook request payload comes with additional information, including the values of the changed attributes *before* the change:


```
POST https://foo.bar/webhooks
```


```json5
{
  data: {
    type: "task",
    id: 1,
    attributes: {
      completed: true,
      completedAt: "2017-01-01T00:00:00",
      state: "completed",
    }
  },
  beforeUpdate: {
    type: "task",
    id: 1,
    attributes: {
      completed: false,
      completedAt: null,
      state: "in progress",
    },
  },
  meta: {
    deliveredAt: "2017-01-01T00:00:01",
    eventName: "task.completed",
  }
}
```

To get version 2 of the webhook data payload, create the webhook with the `payloadVersion` attribute set to `2`:


```
POST https://api.outreach.io/api/v2/webhooks
```


```json5
{
  data: {
    type: "webhook",
    attributes: {
      action: "completed",
      resource: "task",
      payloadVersion: 2,
      url: "https://foo.bar/webhooks",
    },
  },
}
```

## Authenticity validation

For added protection, you can provide the webhook with a `secret` attribute. If that value is present, we'll include
an `Outreach-Webhook-Signature` header that is the HMAC hexdigest of the secret and the payload body, which you can use
to verify the integrity of the request.

For example, to verify the integrity of an incoming webhook in Ruby:


```ruby
SECRET = "foo" # Same as is saved in the Outreach webhook

def verified?(headers, body)
  headers["Outreach-Webhook-Signature"] == signature(body)
end

def signature(body)
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), SECRET, body)
end
```

Outreach does not provide fixed list of IP addresses from which the webhooks are sent.

## Responding to webhooks

It is recommended that a service behind webhook URL responds with `200 OK` upon processing POSTed event.

Outreach does not retry webhook deliveries upon receiving any of the Status Codes including `500 Internal Server Error` and `429 Too Many Requests`.
Some proxies may automatically reply with `429 Too Many Requests` upon exceeding certain limits or purchased capacity and those responses from webhook URLs are taken as acks.
Please ensure appropriate monitoring is in place to notice such events and increase availability of webhook URL service.

Redirects are not followed either.

The timeout while waiting for response is set to 5 seconds. Please ensure that processing of posted events takes less than that or use a message bus for asynchronous processing.

Outreach retries on network related failures such as socket errors, connection resets, etc... There are 4 attempts (3 retries) made to deliver each event to a webhook URL with a one second pause between them.

## Webhook removal

Webhooks configured by 3rd party integrations should be ideally removed by the integration when you, our customer, stop using their, our partner's, services. However, when the tokens get revoked before the cleanup happens, the partner integration loses access to our standard API. That's why we
introduced special single-purpose webhook cleanup token.

The cleanup token is returned on webhook creation in the `cleanupToken` attribute and also it is sent in every request in the `outreach-webhook-cleanup-token` HTTP header so even old webhooks can be cleaned up by the responsible integrators that already lost
ability to call Outreach API but are still receiving customer data.

The cleanup token already contains all the details needed by Outreach and authorizes removal of that particular webhook. No other actions
can be made with this token. Remove the unwanted webhook by sending POST request to https://api.outreach.io/api/v2/webhooks/cleanup and include
the token in the authorization bearer header. No need to send any payload.


```bash
curl -X POST "https://api.outreach.io/api/v2/webhooks/cleanup" -H "Authorization: Bearer eyJh..."
```

The response is always empty and you can interpret the HTTP status code in this way:

- 204: webhook successfully removed
- 404: webhook is already gone
- 401: invalid cleanup token provided


## Webhook de-activation

In case the configured hostname cannot be resolved to a public IP address the webhook gets disabled temporarily. If the hostname resolves to an IP address from the private network IP address range the webhook is disabled for an hour. If the hostname cannot be resolved the DNS request is retried 3-times and the webhook is disabled for a minute if all attempts fail. Webhooks that cannot recover for a week or more are disabled permanently.

## Webhook configuration visibility

Any user or application with access to Outreach and proper scopes can list all the webhooks. However, there are certain attributes that should not be visible to anyone like the `secret` used for payload signing, the `cleanupToken` or sensitive parts of the URL.

Outreach is recording the client ID of the application that created the webhook since 2024. The information is used to limit access to sensitive attributes of webhook configuration from non-admin users and for other applications than the one that created the webhook.

Admin user (not accessing webhooks via an application) can see all the details. Every application can see details of its own webhooks only. Regular users (not accessing webhooks via an application) see all the sensitive attributes redacted.