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

# Event subscriptions

> Send a customer channel's events to extra URLs, each with its own secret and event types, next to the channel's main webhook.

## Event types

| Channel       | Types                                                      |
| ------------- | ---------------------------------------------------------- |
| WhatsApp      | `whatsapp.message.received`, `whatsapp.message.status`     |
| Instagram     | `instagram.message.received`, `instagram.comment.received` |
| Facebook Page | None yet                                                   |

Event examples: [WhatsApp](/whatsapp/event-subscriptions#events), [Instagram](/instagram/event-subscriptions#events). Subscriptions work on customer channels with your organization API key, like every other channel route.

## Create a subscription

```bash theme={null}
curl -X POST https://api.hookmyapp.com/channels/ch_AbCdEf12/subscriptions \
  -H "Authorization: Bearer hmok_..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/new-message","events":["whatsapp.message.received"],"label":"New message flow"}'
```

The response is `201` with `subscriptionId` (`sub_...`) and `secret`. The secret is shown only once: store it. To change the URL or rotate the secret, delete the subscription and create a new one.

* `url`: a public `https://` address.
* `events`: at least one event type the channel supports.
* `label`: optional, up to 120 characters, shown in your delivery log.
* Up to 25 subscriptions per channel.
* Facebook Page channels do not support event subscriptions yet.

## List and delete

* `GET /channels/{ch}/subscriptions` lists a channel's subscriptions, without secrets.
* `DELETE /channels/{ch}/subscriptions/{sub}` removes one. Deleting it again is harmless. A delivery already in progress may still arrive once.

## Verify the signature

Every delivery carries `X-HookMyApp-Signature-256: sha256=<hex>`: an HMAC-SHA256 of the raw request body, keyed with the subscription's secret. It works exactly like [your main webhook's signature](/whatsapp/receive-webhooks#signature-verification).

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

function isSigned(rawBody, header, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return typeof header === 'string' && header.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
```

## Delivery and retries

* Events usually arrive within seconds.
* Answer with any `2xx` within 10 seconds.
* Any other answer, a redirect or a timeout is retried with growing delays for about 3 hours. After that the event is dropped for that subscription.
* While one subscription's endpoint keeps failing, deliveries to other subscriptions can be briefly delayed.
* An event can arrive more than once, and events can arrive out of order. Drop duplicates by `id`; order by `occurredAt`.
* Every attempt shows in your delivery log with the subscription's label.
* Your main webhook destination does not change. It keeps receiving what it receives today, and a failing subscription never affects it.

## Sample events

`GET /channels/{ch}/events?types=whatsapp.message.received&limit=3` returns recent events in the same shape, one per `id`, with `raw` set to `null` and `rawOmitted` set to `true`. It returns up to `limit` of the most recent events, newest first, and it looks through a bounded number of recent deliveries, so it can return fewer than `limit` events when the type you asked for is rare. Use it as sample data when you set up an automation, not as a history feed.

## For automation platform builders

If you build a trigger for an automation platform on top of HookMyApp:

* **Subscribe** (a user turns the trigger on): `POST /channels/{ch}/subscriptions` with the event type the trigger listens for and your platform's target URL. Store the returned `subscriptionId` (and the `secret`, to verify deliveries).
* **Unsubscribe** (the trigger is turned off or deleted): `DELETE /channels/{ch}/subscriptions/{subscriptionId}`.
* **Sample data** (while the user sets up the trigger): `GET /channels/{ch}/events?types=<event type>&limit=3`.
* **Deduplication:** use the event `id`. The same event always has the same `id`, including when it is delivered more than once.

## Errors

| Code                             | Status | Meaning                                                              |
| -------------------------------- | ------ | -------------------------------------------------------------------- |
| `CHANNEL_ACTION_INPUT_INVALID`   | 400    | Body is malformed, `events` is empty or unknown, `label` is too long |
| `CHANNEL_EVENT_TYPE_UNSUPPORTED` | 400    | The channel cannot produce that event type                           |
| `WEBHOOK_URL_INVALID`            | 400    | The URL is not `https` or not a public address                       |
| `SUBSCRIPTION_NOT_FOUND`         | 404    | No such subscription on this channel                                 |
| `SUBSCRIPTION_LIMIT_REACHED`     | 409    | The channel already has 25 subscriptions                             |
