Webhooks

Signed events for everything that happens on your accounts.

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 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

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).

EventWhen
AccountStatusAn 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_receivedA new message in a LinkedIn conversation or a WhatsApp chat, the account's own included (is_sender).
new_relationA LinkedIn member accepted the account's invitation.
invitation_receivedSomeone invited the LinkedIn account to connect.
message_delivered, message_read, message_edited, message_deleted, message_reactionA WhatsApp message changed, or a reaction was added or taken back.
mail_received, mail_sentA mail arrived in a mailbox's inbox, or a mail sent through the API has left.
mail_opened, mail_link_clickedA 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 lists every event with its fields.

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:

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 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.

On this page