> For the complete documentation index, see [llms.txt](https://docs.kick.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kick.co/api/webhooks.md).

# Webhooks

Kick pushes events to an HTTPS endpoint you own. Delivery runs on [Svix](https://www.svix.com), which signs every request, retries failures on a fixed schedule, and hosts the portal where you register endpoints and inspect deliveries.

Endpoints belong to the organization rather than to a workspace, so a single endpoint covers every workspace in your book of business.

{% hint style="info" %}
Webhooks are enabled with the rest of the Platform API, per organization. If your organization does not have access yet, email <platform@kick.co>.
{% endhint %}

### Register an endpoint

An organization admin registers endpoints from the same page that holds the platform token:

1. In your organization, click **API** in the left navigation.
2. Under **Webhooks**, click **Open portal**.
3. Add an endpoint and paste the HTTPS URL that will receive the events.
4. Subscribe the endpoint to the event types you want. Leaving the selection empty subscribes it to all of them.
5. Copy the endpoint's signing secret. It starts with `whsec_` and is the key you verify deliveries with.

The portal is also where you inspect delivery logs, retry a single message, and replay failed events.

### Event types

Each event has its own page holding the payload it carries and the conditions it fires under:

| Event                                                                             | Sent when                                                                                                           |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| [`plaid.connection.disconnected`](/api/webhooks/plaid-connection-disconnected.md) | A Plaid connection of one of your business entities stopped syncing and has to be reconnected by the business owner |

***

### The event envelope

Every event arrives in the same envelope, with the fields specific to it under `payload`:

```json
{
  "event": "plaid.connection.disconnected",
  "version": 1,
  "occurredAt": 1723363200,
  "payload": {}
}
```

| Field        | Type    | Notes                                                                                                                                                                                                          |
| ------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`      | string  | The event type. Switch on it to route the delivery, rather than inferring the type from the endpoint it arrived on                                                                                             |
| `version`    | integer | Version of the envelope and the payloads it carries, shared by every event type. It is raised when the shape changes in a way that could break a handler, so pin your parsing to the version you built against |
| `occurredAt` | integer | Unix time in seconds, not the ISO 8601 timestamp the REST endpoints return                                                                                                                                     |
| `payload`    | object  | The event's own data, documented on the event's page                                                                                                                                                           |

Because the body names its own event type, one endpoint subscribed to everything can serve your whole integration. Ids inside `payload` are the same uuids the REST API uses, so a `workspaceId`, `entityId`, or resource id can be passed straight to the endpoints in the reference.

***

### Verify the signature

Every delivery carries three headers:

| Header           | Holds                                                            |
| ---------------- | ---------------------------------------------------------------- |
| `svix-id`        | Message id, the same value across every retry of one message     |
| `svix-timestamp` | Unix time in seconds at which the attempt was signed             |
| `svix-signature` | Space delimited list of base64 signatures, one per active secret |

The signature is a base64 HMAC-SHA256 of `{svix-id}.{svix-timestamp}.{body}`, keyed on the base64 decoded part of the signing secret after the `whsec_` prefix. Check it against the raw request body: parsing the JSON and serializing it again changes the bytes and breaks verification.

The [Svix libraries](https://docs.svix.com/receiving/verifying-payloads/how) do all of that, including rejecting a delivery whose timestamp is more than five minutes out, which is what stops a captured request from being replayed at you:

```javascript
import express from "express";
import { Webhook } from "svix";

const app = express();
const wh = new Webhook(process.env.KICK_WEBHOOK_SECRET);

app.post(
  "/kick/webhooks",
  express.raw({ type: "application/json" }),
  (req, res) => {
    let event;

    try {
      event = wh.verify(req.body, req.headers);
    } catch {
      return res.sendStatus(400);
    }

    switch (event.event) {
      case "plaid.connection.disconnected":
        queueReconnectReminder(event.payload);
        break;
    }

    res.sendStatus(204);
  },
);
```

{% hint style="warning" %}
The headers reach some receivers with a `webhook-` prefix instead of `svix-`. The official libraries accept either, so verify with one rather than reading a single prefix by hand.
{% endhint %}

### Delivery and retries

Answer with any `2xx` within 15 seconds. Everything else, including a `3xx` redirect and a timeout, counts as a failure and is retried immediately, then after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours again. After the last attempt the message is marked failed, and you recover it from the portal.

An endpoint that keeps failing for five days is disabled, so check the delivery logs if events stop arriving.

Retries reuse the same `svix-id`, which makes it the idempotency key for your handler. Acknowledge the delivery first and do the work afterwards when processing can outrun the 15 second window.

***

### Next steps

→ [plaid.connection.disconnected](/api/webhooks/plaid-connection-disconnected.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kick.co/api/webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
