> ## Documentation Index
> Fetch the complete documentation index at: https://docs.madiad.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive a signed callback when a post publishes or fails.

Webhooks push events to your server so you don't have to poll. They're the reliable way to learn the outcome of scheduled posts and asynchronous (video) uploads.

## Subscribe

Create a webhook endpoint from your dashboard at [hub.madiad.com/dashboard/webhooks](https://hub.madiad.com/dashboard/webhooks): enter the URL that should receive deliveries and pick the events you want. The dashboard generates a `secret` for that endpoint — store it. You'll use it to verify every delivery.

## Events

| Event                        | Fires when                                                                         |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| `post.completed`             | An upload finished processing on a platform — success or failure is in the payload |
| `connection.connected`       | A social account was linked to a profile                                           |
| `connection.disconnected`    | A social account was unlinked from a profile                                       |
| `connection.reauth_required` | A connected account needs to be re-authorized                                      |

Each delivery is a JSON body shaped `{ "id", "type", "created_at", "data": { … } }`. For `post.completed`, `data` carries `profile_id`, `platform`, `media_type`, `success`, `url`, `publish_id`, and `error`.

## Verify the signature

Every request carries an `X-MADIAD-Signature` header in the form `sha256=<hex>`: an HMAC-SHA256 of the **raw request body**, keyed with your subscription `secret`. Recompute it and compare before trusting the payload. Each delivery also includes `X-MADIAD-Event` and `X-MADIAD-Delivery` headers.

```js theme={null}
import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

<Warning>
  Verify against the **raw, unparsed** body. Re-serializing parsed JSON changes the bytes and the signature will not match.
</Warning>

## Respond and retries

* Return a `2xx` status quickly (within a few seconds) to acknowledge receipt.
* Any non-`2xx` response or a timeout is retried with exponential backoff.
* Make your handler **idempotent** — a delivery can arrive more than once. Dedupe on the delivery `id` (also sent as the `X-MADIAD-Delivery` header).

<Tip>
  Do slow work (database writes, downstream calls) *after* you respond `2xx` — for example by enqueueing the event — so you never trip the delivery timeout.
</Tip>
