# Webhooks (/raw-api/webhooks)



Swarmhit POSTs what happens on your accounts (new messages, accepted invitations, status
changes, sends, opens, bounces) to **your webhook URL**. Set it in the
[console](https://console.swarmhit.com) on the **Webhook** page: an `https://` address on
the public internet, with its own signing secret (`whsec_…`, shown once; rotate it
there). The sandbox has its own webhook, set apart from the live one.

With no webhook on, your events are not kept for later. *Send a test event* in the
console sends a signed `webhook.test`.

## The events [#the-events]

Every body carries `event_id`, `emitted_at` and `livemode` (`false` for a sandbox
account's events). An account status change comes as `{ "AccountStatus": { account_id,
message, … } }`; every other event names itself in `event`, with its `account_id` (the id's
prefix, `lg_`, `wa_` or `em_`, says the channel).

| Event                                                                                        | When                                                                                                                                                                                                                                        |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AccountStatus`                                                                              | An account's status changed: `OK`, `CREDENTIALS` (reconnect it), `STOPPED` with a `reason`, `DELETED`; WhatsApp numbers and mailboxes also send `CREATION_SUCCESS`, `RECONNECTED`, `CONNECTING`, `ERROR` (and `SYNC_SUCCESS` for WhatsApp). |
| `message_received`                                                                           | A new message in a LinkedIn conversation or a WhatsApp chat, the account's own included (`is_sender`).                                                                                                                                      |
| `new_relation`                                                                               | A LinkedIn member accepted the account's invitation.                                                                                                                                                                                        |
| `invitation_received`                                                                        | Someone invited the LinkedIn account to connect.                                                                                                                                                                                            |
| `message_delivered`, `message_read`, `message_edited`, `message_deleted`, `message_reaction` | A WhatsApp message changed, or a reaction was added or taken back.                                                                                                                                                                          |
| `mail_received`, `mail_sent`                                                                 | A mail arrived in a mailbox's inbox, or a mail sent through the API has left.                                                                                                                                                               |
| `mail_opened`, `mail_link_clicked`                                                           | A tracked mail was opened, or one of its links clicked.                                                                                                                                                                                     |

The `gateway.*` events are Swarmhit's own additions, more detail you can ignore unless you
need it, with a few you will want: `gateway.checkpoint` and `gateway.sign_in_failed` (the
outcome of a LinkedIn sign-in after its call returned), and `gateway.mail_bounced`,
`gateway.mail_complaint` and `gateway.mail_failed` (the fate of a sent mail). The
[API reference](/raw-api/reference) lists every event with its fields.

## Verifying a delivery [#verifying-a-delivery]

Each request carries two headers:

* `X-LG-Signature`: `base64url(HMAC-SHA256(raw body, your signing secret))`.
* `X-LG-Event-Id`: the event's id (also `event_id` in the body).

Verify the signature over the **raw bytes**, before parsing:

```js
import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.post('/swarmhit/events', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = crypto
    .createHmac('sha256', process.env.SWARMHIT_WEBHOOK_SECRET) // your whsec_…
    .update(req.body)
    .digest('base64url');
  const given = String(req.get('x-lg-signature') || '');
  const valid = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200); // answer first, then handle it, once per event.event_id
  handleOnce(event.event_id, event);
});
```

## Delivery [#delivery]

Delivery is **at least once**. Events are written first, then sent. An answer other than
2xx within 15 seconds is retried after 1 min, 5 min, 15 min, 1 h, 6 h, 12 h, 24 h and 24 h
(about 3 days), then given up. When your endpoint answers again after an outage,
everything waiting for it is sent at once. Redirects are not followed.

* **Deduplicate** on the event id: a retry keeps its id, and a message seen twice by
  Swarmhit is still one event with one id.
* **Answer 2xx fast** and do the work after.
* **Do not rely on order.** For an account's status, keep the one with the newest
  `emitted_at`.
