Zernio
Zernio
OverviewWebhooksPost webhooksInbox webhooksAccount webhooksAnalytics webhooksAds webhooksCall webhooksWhatsApp webhooksPhone number webhooks
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources

Webhooks

Create a webhook endpoint, receive your first event, and verify, deduplicate and retry deliveries the way Zernio sends them.


Zernio POSTs an event to your webhook endpoint when something happens: a post publishes, a DM arrives, an account disconnects, a number activates. Enable it with POST /v1/webhooks/settings. The secret Zernio signs deliveries with is the secret you send in that call; Zernio never generates one for you, and the same endpoints are editable in the webhooks dashboard.

First event

Call POST /v1/webhooks/settings with name, url, events and a secret. You can create up to 50 endpoints per user.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: created } = await zernio.webhooks.createWebhookSettings({
  body: {
    name: 'Production',
    url: 'https://example.com/webhooks/zernio',
    secret: process.env.ZERNIO_WEBHOOK_SECRET,
    events: ['post.published', 'post.failed', 'message.received']
  }
});

const webhookId = created.webhook._id;

Response (200):

{
  "success": true,
  "webhook": {
    "_id": "507f1f77bcf86cd799439011",
    "name": "Production",
    "url": "https://example.com/webhooks/zernio",
    "events": ["post.published", "post.failed", "message.received"],
    "isActive": true,
    "failureCount": 0
  }
}

webhook._id is the webhookId for the test call. Call POST /v1/webhooks/test to send a webhook.test event to the endpoint right away:

const { data: tested } = await zernio.webhooks.testWebhook({
  body: { webhookId }
});

console.log(tested.message);

Response (200):

{
  "success": true,
  "message": "Test webhook sent successfully"
}

Your endpoint receives one POST with these headers and body:

POST /webhooks/zernio HTTP/1.1
Content-Type: application/json
User-Agent: Zernio-Webhooks/1.0
X-Zernio-Event: webhook.test
X-Zernio-Event-Id: 3f0c1c2e-6c4a-4d3e-9b1f-2a7d8e9f0a1b
X-Zernio-Signature: 5d41402abc4b2a76b9719d911017c592e4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9
{
  "id": "3f0c1c2e-6c4a-4d3e-9b1f-2a7d8e9f0a1b",
  "event": "webhook.test",
  "message": "This is a test webhook from Zernio",
  "timestamp": "2027-01-01T17:00:00Z"
}

Return any 2xx within 5 seconds. webhook.test reaches the endpoint whatever its events list says, so it works before you subscribe to anything real. A 500 from the test call means your endpoint did not answer 2xx:

{
  "success": false,
  "message": "Test webhook failed"
}

Check the endpoint's URL, then read the attempt in webhook logs or the dashboard.

Events

Every event is one event name in the events array. Each area page documents the payload of every event it covers.

AreaEvents
Postspost.scheduled, post.platform.published, post.platform.failed, post.published, post.partial, post.failed, post.tiktok.url_resolved, post.platform.deleted, post.cancelled, post.recycled, post.external.created, post.external.updated, post.external.deleted
Inboxmessage.received, message.sent, conversation.started, conversation.control_changed, message.edited, message.deleted, message.delivered, message.read, message.failed, reaction.received, referral.received, comment.received, review.new, review.updated
Accountsaccount.connected, account.disconnected
Analyticsanalytics.synced
Adsaccount.ads.initial_sync_completed, lead.received, ad.status_changed
Callscall.received, call.ended, call.failed, call.permission_request
WhatsAppwhatsapp.template.status_updated, whatsapp.template.category_updated, whatsapp.account.name_status_updated, whatsapp.automatic_event
Phone numberswhatsapp.number.kyc_submitted, whatsapp.number.activated, whatsapp.number.declined, whatsapp.number.action_required, whatsapp.number.verification_required, whatsapp.number.suspended, whatsapp.number.reactivated, whatsapp.number.released, phone_number.stock_available
Verifyverification.approved, verification.failed

Subscribe an endpoint only to the events it handles. Manage endpoints with List webhooks, Update webhook settings and Delete webhook settings.

Verification events

The two managed-OTP events have no area page: they belong to a verification, not to a connected account. verification.approved fires when the recipient submits the right code to Check a verification code, verification.failed when the attempts run out, with reason: "max_attempts_reached". Both carry verification.verificationId, verification.channel (sms) and verification.to. Send the code with Send a verification code.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

Example Requests

POST/verification.approved

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

Example Requests

POST/verification.failed

How it behaves

Delivery retries

Zernio delivers an event up to 7 times, counting the first attempt. A delivery succeeds when your endpoint returns a 2xx within 5 seconds. Any other outcome (a non-2xx status, a timeout, a connection error) schedules the next attempt on an exponential backoff capped at 24 hours, measured from the moment the previous attempt finished:

AttemptDelay before this attemptCumulative time since the first attempt
1immediate0
210s~10s
31m 40s~1m 50s
416m 40s~18m 30s
52h 46m 40s~3h 5m
624h (capped)~27h 5m
724h (capped)~51h 5m

After the 7th failure Zernio moves the event to a dead-letter queue and stops retrying it. Every attempt is visible in webhook logs and the dashboard; attemptNumber says which try produced each entry, and Redeliver sends a dead-lettered event again.

To stay under the 5-second limit, persist the event and return 2xx, then process it on a background worker.

Zernio disables an endpoint only when it has had no successful delivery for 3 days and has either reached 20 consecutive terminal failures (events that exhausted every attempt) or been failing continuously for 3 days. One successful delivery inside that window keeps it enabled whatever the count. Zernio emails the owner; re-enable it with isActive: true on Update webhook settings. You can also pause or remove an endpoint yourself at any time.

At-least-once delivery

Zernio delivers every event at least once. The same event can arrive twice when a previous attempt's response was lost or your endpoint took longer than 5 seconds to answer, so your handler must be idempotent.

Every payload carries a stable event id, repeated as a header:

  • payload.id, the canonical event id (UUID).
  • X-Zernio-Event-Id, the same value as a header.
  • X-Late-Event-Id, the legacy alias, kept for backward compatibility.

Use it as your deduplication key: insert the id into a unique-indexed table or cache before processing, and skip the payload when the insert conflicts.

Signatures

Zernio signs every delivery when the endpoint has a secret. X-Zernio-Signature is the lowercase hex HMAC-SHA256 of the raw request body keyed by that secret; X-Late-Signature is the legacy alias with the same value. Read the raw body, compute the HMAC and compare:

import crypto from "crypto";

export const POST = async (req: Request) => {
  const webhookSignature = req.headers.get("X-Zernio-Signature");
  if (!webhookSignature) {
    return new Response("No signature provided.", { status: 401 });
  }

  const secret = process.env.ZERNIO_WEBHOOK_SECRET;
  if (!secret) {
    return new Response("No secret provided.", { status: 401 });
  }

  const rawBody = await req.text();

  const computedSignature = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  if (webhookSignature !== computedSignature) {
    return new Response("Invalid signature", { status: 400 });
  }

  const payload = JSON.parse(rawBody);
  // Handle the webhook event
  // ...
};

A mismatch means the request did not come from Zernio or the body changed in transit. Do not process it.

The test event

Zernio sends webhook.test to an endpoint when you call Test webhook or press test in the dashboard, whatever events the endpoint subscribes to. It is sent once, synchronously, and is never retried.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Webhook payload for test deliveries

Response Body

Example Requests

Webhook payload for test deliveries

POST/webhook.test

Related

  • Create webhook settings: every field, including customHeaders and disabledResourceGroups.
  • Webhook logs: every attempt with its status code and response body.
  • Post webhooks: the events behind a scheduled post.
  • Inbox webhooks: message.received and what it carries.
  • Multi-tenant: route events to the right customer by account.profileId.
Was this page helpful?

Tools

The parameters of the 20 core Zernio MCP tools, the tools/list call that returns the catalog your client sees, and the browser upload flow for media.

Post webhooks

Receive an event at every step of a post's publishing lifecycle, per platform, and for posts authored natively on the platform.

On this page

First eventEventsVerification eventsHow it behavesDelivery retriesAt-least-once deliverySignaturesThe test eventRelated
id?string
event?"verification.approved"

Value in

  • "verification.approved"
timestamp?string

UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.

Formatdate-time
verification?
id?string
event?"verification.failed"

Value in

  • "verification.failed"
timestamp?string

UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.

Formatdate-time
verification?
reason?"max_attempts_reached"

Value in

  • "max_attempts_reached"
id*string

Stable webhook event ID

event*"webhook.test"

Value in

  • "webhook.test"
message*string

Human-readable test message

timestamp*string

UTC time at which Zernio generated this test event (set once when the payload is built). Test fires are sent synchronously as a single attempt; a later redelivery of this event keeps the original value.

Formatdate-time