Lynkist Developers

Webhooks

Receive signed, retried events for messages, contacts, follow-ups and campaigns.

Webhooks let you react to events inside Lynkist without polling. When something interesting happens — a message is delivered, a contact opts out, a campaign completes — we POST a signed JSON payload to an HTTPS endpoint you configure.

This page describes the receive-side contract: events, headers, signatures, retries. For the API to manage endpoints (create, list, rotate-secret, retry deliveries), see the Webhook management API.

How it works

  1. You register an endpoint URL via the dashboard or the Webhook management API and subscribe it to one or more event types.
  2. When a subscribed event fires, Lynkist enqueues a delivery and POSTs a signed JSON body to your URL.
  3. Your endpoint returns a 2xx status within 10 seconds.
  4. If it does not, we retry on a fixed back-off curve for up to 5 more attempts.
  5. After 20 consecutive failures across all deliveries, the endpoint is auto-disabled and stops receiving events until you reactivate it.

Event catalog

Every value below is an event Lynkist actually emits today.

Contacts (contact.*)

EventWhen
contact.createdA contact was added (dashboard, import, or API).
contact.updatedAny field on a contact changed.
contact.deletedA contact was archived (soft-delete).
contact.note_addedA note was attached to a contact.
contact.communication_addedA communication-history row was attached to a contact.

Follow-ups (followup.*)

EventWhen
followup.createdA follow-up reminder was scheduled.
followup.updatedA follow-up was edited or marked done.
followup.deletedA follow-up was removed.

Messages (message.*)

EventWhen
message.sentMeta accepted the outbound message.
message.deliveredMeta confirmed delivery to the recipient device.
message.readThe recipient opened the message (only when read receipts are enabled).
message.failedMeta reported a permanent send failure (includes the error code).
message.receivedAn inbound message arrived from a user.

Campaigns (campaign.*)

EventWhen
campaign.createdA new campaign was created.
campaign.scheduledA campaign was scheduled for a future start time.
campaign.startedDispatch began (either at the scheduled time or via "Send now").
campaign.pausedA running or scheduled campaign was paused.
campaign.resumedA paused campaign was resumed.
campaign.stoppedA campaign was permanently stopped (cannot be resumed).
campaign.completedAll recipients were processed.
campaign.failedThe campaign aborted before completion (e.g. fatal template/WABA error).

Webhook lifecycle (webhook.*)

EventWhen
webhook.testYou triggered a test delivery from the dashboard or via POST /webhooks/{id}/test.

Template lifecycle events (template.approved, template.rejected, …) are not emitted today. They will appear in this catalogue when wired up — track Changelog.

Request headers

Every delivery includes these headers:

HeaderValue
Content-Typeapplication/json
User-AgentLynkist-Webhook/1.0
X-Lynkist-EventThe event type, e.g. message.delivered
X-Lynkist-Delivery-IDUUID of this delivery attempt — unique per attempt
X-Lynkist-Webhook-IDUUID of your endpoint
X-Lynkist-TimestampUnix epoch seconds when the request was signed
X-Lynkist-Signaturesha256=<hex> — see Signature verification

Test deliveries additionally carry X-Lynkist-Verification: true.

Payload shape

Every webhook payload follows the same envelope:

{
  "id": "evt_01HW3K…",
  "type": "message.delivered",
  "created_at": "2026-05-31T08:30:01Z",
  "data": {
    "message_id": "wamid.HBgM…",
    "to": "+91…",
    "delivered_at": "2026-05-31T08:30:01Z"
  }
}

The data object varies by event type. Stay forward-compatible: ignore unknown fields rather than failing on them.

Signature verification

The X-Lynkist-Signature value is sha256=<hex> where <hex> is an HMAC-SHA256 over the string f"{timestamp}.{body}", keyed by the endpoint's signing secret. The timestamp is the exact value in the X-Lynkist-Timestamp header (seconds since epoch, as a string).

Always reconstruct the signed payload from the raw request body — re-serialising parsed JSON will not round-trip and the signature will not match.

Node.js (TypeScript)

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyLynkistSignature({
  rawBody,
  timestamp,
  signatureHeader,
  secret,
}: {
  rawBody: string
  timestamp: string
  signatureHeader: string  // e.g. "sha256=abc..."
  secret: string
}): boolean {
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')

  const a = Buffer.from(expected)
  const b = Buffer.from(signatureHeader)
  return a.length === b.length && timingSafeEqual(a, b)
}

Python

import hmac, hashlib

def verify_lynkist_signature(raw_body: bytes, timestamp: str, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

Verify before you trust. Without signature checking, anyone who guesses your endpoint URL can POST fake events. Reject any request whose signature does not match — and consider rejecting any request whose X-Lynkist-Timestamp is more than a few minutes old to thwart replay attacks.

Retries

If your endpoint returns a non-2xx response or does not reply within 10 seconds, the delivery is retried on this fixed back-off curve:

AttemptDelay before this attempt
1— (initial)
21 min
35 min
430 min
52 hr
66 hr

After the 6th failed attempt the delivery is marked abandoned. The delivery row stays queryable via the Webhook management API for replays and audits.

After 20 consecutive failures across an endpoint (any combination of deliveries), the endpoint itself is auto-disabled — its is_active flag flips to false and no further events are queued for it. Reactivate by editing the endpoint via the API or dashboard.

Best practices

  • Return 2xx fast. Acknowledge the event, then process asynchronously. A slow handler triggers unnecessary retries and burns through your error budget.
  • Verify the signature. Always — even for "internal" endpoints behind a firewall.
  • Deduplicate on the envelope id. Network conditions can deliver the same event more than once.
  • Subscribe selectively. Pick the event types you actually use; you can always add more later by PUTing the endpoint.
  • Test with POST /webhooks/{id}/test. This emits a webhook.test envelope and is the cleanest way to validate your signature code end-to-end.
  • Reactivate auto-disabled endpoints carefully. If we disabled an endpoint, something at your end was repeatedly broken. Fix it before flipping the flag.
Webhooks — Lynkist Developers | Lynkist