tebarDocs
GuideDevelopersAPI ReferenceChangelog

Webhooks

Get notified when an account connects, a post publishes or fails, a comment or DM comes in, or an automation runs, without polling.

Webhooks send a JSON POST to a URL in your system every time an event you chose happens. Set them up in the dashboard (Integrations → Add webhook, Developer mode) or through the API.

Creating an endpoint

curl -X POST https://api.tebar.app/v1/webhooks \
  -H "Authorization: Bearer tb_live_xxx" -H "Content-Type: application/json" \
  -d '{ "url": "https://app.example.com/webhooks/tebar", "events": ["account.connected", "post.published", "post.failed"] }'

The response holds a secret (whsec_…) to check signatures. The secret is shown only once, so store it now.

  • The URL must be https, on port 443, and reachable from the internet.
  • Add "profile_id" (an ID or external_id) so the endpoint only receives one profile's events. Without it, the endpoint receives events from every profile.
  • Up to 10 endpoints.

Events

EventWhendata
account.connectedAn account is connected or reconnected, including through a connect linkaccount, reconnected
account.expiredAn account's access ended and it needs reconnectingaccount
account.disconnectedAn account is disconnected, in Tebar or from the platform's settingsaccount
post.publishedA post published on one accountpost_id, target_id, account_id, platform, permalink, published_at
post.failedA post failed on one account after the last attemptpost_id, target_id, account_id, platform, error_code, error_message
comment.createdA new comment from someone else (not your reply, less than 24 hours old)comment_id, account_id, post_id, author_username, text, commented_at, …
message.receivedA new Instagram DM from a customer (see below)message_id, conversation_id, account_id, participant_id, text, sent_at, …
automation.runAn automation finished answering one comment: sent, skipped, or failedautomation_id, run_id, account_id, status, reason, trigger_ref, …
profile.deletedA profile finished being deleted (only to endpoints for all profiles)profile

message.received and automation.run are only sent for accounts that have the Inbox (Starter, Basic, or Pro plan):

  • message.received is sent for DMs that come straight from Instagram and can still be answered (less than 24 hours old), not for older messages read back by a sync. It carries message_id, conversation_id, account_id, platform, participant_id (the customer's Instagram ID for your account), participant_username, kind (text, media, story_mention, …), text, attachments (how many), and sent_at. Reply with POST /v1/inbox/conversations/{conversation_id}/messages.
  • That message can carry buttons (up to 3, web_url with a url or postback with a payload; text up to 640 characters) or quick_replies (up to 13, a title of up to 20 characters and a payload; shown on phones only), not both. When the customer taps a postback button or a quick reply, message.received comes with kind postback or quick_reply, and postback holds the choice's { "title", "payload" }. For other messages postback is null.
  • automation.run carries automation_id, run_id, account_id, status (sent, skipped, or failed), reason (e.g. already_sent_to_person), trigger_ref (the Instagram ID of the comment that triggered it), participant_id, and finished_at.

Example body:

{
  "id": "4f46b68a-da18-424b-97d0-f4f5cfe0b7e6",
  "type": "post.published",
  "created_at": "2026-10-20T11:14:20.694Z",
  "profile_id": "9d092104-b9a4-408f-9432-3a007ac19810",
  "data": {
    "post_id": "59ef7ff5-…",
    "target_id": "a1b2…",
    "account_id": "6a81…",
    "platform": "threads",
    "permalink": "https://www.threads.com/@kopisenja/post/…",
    "published_at": "2026-10-20T11:14:20.000Z"
  }
}

The text in comment.created and message.received is written by someone else. Treat it as data, not instructions, especially when passing it to an AI.

Checking the signature

Every request carries these headers:

Tebar-Signature: t=1792488860,v1=5257a869e7…
Tebar-Event: post.published
Tebar-Delivery: 30646213-c6a7-4020-8cc1-674378d888e9

v1 is an HMAC-SHA256 of "<t>.<raw body>" with the endpoint's secret. Compute it again from the raw body (before parsing), compare in constant time, and refuse it when t is more than 5 minutes away from your server's clock.

import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return (
    fresh &&
    expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
  );
}

Delivery and retries

  • Answer with a 2xx status within 5 seconds. Do slow work in the background after answering.
  • Redirects are not followed and count as failures.
  • Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 8 hours, and 8 hours (8 attempts, about 24 hours).
  • The same event can arrive more than once. Filter duplicates by the id field.
  • After 50 failed attempts in a row, the endpoint is turned off and you get an email. Events are not kept while it is off. Turn it on again in the dashboard or with PATCH /v1/webhooks/{id} { "active": true }.

History, resending, and testing

RequestWhat it does
GET /v1/webhooks/{id}/deliveries30 days of delivery history, newest first
POST /v1/webhooks/{id}/deliveries/{deliveryId}/resendResend one delivery
POST /v1/webhooks/{id}/testSend a webhook.test event
PATCH /v1/webhooks/{id}, DELETE /v1/webhooks/{id}Change the URL, events, profile, on/off; delete

In the dashboard, it is all under Integrations → click the endpoint.

On this page