Tesselith
ProductSolutionsCompareFree toolsBlogPricing
Join the waitlist
  1. Home/
  2. Docs/
  3. Webhooks: Real-Time Events From Tesselith

Webhooks

Webhooks: Real-Time Events From Tesselith

Updated October 30, 2026

Get a signed HTTP request when posts publish or fail, queues change or channels disconnect. Verify Standard Webhooks signatures, handle retries, replay.

Webhooks let Tesselith tell your systems when something happens, instead of your code asking over and over. Add an HTTPS endpoint and choose the events you care about. Within seconds of a post publishing, failing or being scheduled, Tesselith sends your URL a small signed JSON request. Signatures follow the open Standard Webhooks specification, so you can verify them with an official library in your language instead of hand-rolling HMAC code. Webhooks are included on the Pro, Team and Agency plans.

Key takeaways

  • Add endpoints in Settings → Developers → Webhooks (owners and admins) or with POST /v1/webhooks.
  • Verify every request with your endpoint's whsec_ secret using a Standard Webhooks library.
  • Answer with any 2xx within 15 seconds. Do slow work after responding.
  • Deduplicate on the webhook-id header: retries and replays reuse it.
  • Failed deliveries retry for about 27 hours. An endpoint that fails for 5 days straight is switched off and its owners are emailed.

Events

EventSent whendata
post.createdA post is created in the app, the API or by an AI assistantpost_id
post.scheduledA post is given a time, by a date or a queue slotpost_id, scheduled_at
post.publishedA post finished publishing; status is published or partially_published when some channels failedpost_id, status
post.failedEvery channel failed to publishpost_id
post.deletedA post is deletedpost_id
queue.updatedA channel's queue changed (schedule edited, posts reordered, queue paused or resumed)channel_id
channel.connectedA social account is connectedchannel_id
channel.disconnectedA social account is removedchannel_id
channel.reconnect_requiredA network revoked or expired the authorization; posts to it will fail until someone reconnectschannel_id

Leave the event list empty to receive everything, including event types added later. Pick specific events if you'd rather opt in to each one.

What a request looks like

POST /hooks/tesselith HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: Tesselith-Webhooks/1.0 (+https://tesselith.com)
webhook-id: msg_2T4sC9xPq8LmVb1Nz0aQe
webhook-timestamp: 1793361000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

{"type":"post.published","timestamp":"2026-11-03T14:30:00.512Z","data":{"post_id":"V1StGXR8_Z5jdHi6B-myT","status":"published"}}

Payloads are deliberately thin. They say what happened and which object it happened to, not the whole object. When you need the post's current state, fetch it from GET /v1/posts/{post_id}. Deliveries can arrive late or out of order after retries, and an id-only payload means you always act on current data rather than a snapshot from an hour ago.

Verifying signatures

Anyone can send a POST to a public URL, so check every request before trusting it. The signature is an HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw body}, keyed with your endpoint's secret. Verification libraries also reject timestamps more than five minutes off, which stops someone replaying an old request. Use the raw request body exactly as received: parsing the JSON and re-serializing it changes the bytes and breaks the signature.

Node.js (Express)

// npm install standardwebhooks express
import express from "express"
import { Webhook } from "standardwebhooks"

const wh = new Webhook(process.env.TESSELITH_WEBHOOK_SECRET!) // "whsec_…"
const app = express()

app.post(
  "/hooks/tesselith",
  express.raw({ type: "application/json" }), // keep the raw bytes
  (req, res) => {
    let event
    try {
      event = wh.verify(req.body.toString("utf8"), req.headers as Record<string, string>)
    } catch {
      return res.status(401).send("bad signature")
    }
    res.sendStatus(204)        // acknowledge first…
    void handle(event, req.header("webhook-id")!) // …then do the work
  }
)

Next.js route handler

// app/api/tesselith/route.ts
import { Webhook } from "standardwebhooks"

const wh = new Webhook(process.env.TESSELITH_WEBHOOK_SECRET!)

export async function POST(req: Request) {
  const body = await req.text()
  try {
    const event = wh.verify(body, Object.fromEntries(req.headers)) as {
      type: string
      data: { post_id?: string; channel_id?: string; status?: string }
    }
    if (event.type === "post.failed") await alertTeam(event.data.post_id!)
  } catch {
    return new Response("bad signature", { status: 401 })
  }
  return new Response(null, { status: 204 })
}

Python (Flask)

# pip install standardwebhooks flask
import os
from flask import Flask, request
from standardwebhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["TESSELITH_WEBHOOK_SECRET"])  # "whsec_…"
app = Flask(__name__)

@app.post("/hooks/tesselith")
def tesselith():
    try:
        event = wh.verify(request.get_data(), dict(request.headers))
    except WebhookVerificationError:
        return "bad signature", 401
    if event["type"] == "post.published":
        mark_published(event["data"]["post_id"], event["data"]["status"])
    return "", 204

Official Standard Webhooks libraries also exist for Go, Ruby, PHP, Rust, Java, Kotlin and C#. They all take the same secret and the same three headers.

Verifying by hand

If you can't use a library: strip the whsec_ prefix and base64-decode the rest to get the key bytes. Compute HMAC-SHA256 over id + "." + timestamp + "." + body and base64-encode it. The webhook-signature header holds one or more space-separated v1,<signature> values, and the request is valid if any of them equals yours (compare in constant time). Reject timestamps more than 5 minutes from your clock.

Responding and retries

Any 2xx status within 15 seconds counts as delivered. Anything else is a failure: a 3xx, 4xx or 5xx status, a timeout, a refused connection or a TLS error. Redirects are not followed. Failures retry on this schedule, measured from the previous attempt:

AttemptDelay after the previous one
1Immediately
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
810 hours

That's eight attempts over about 27 hours. After the last one the delivery is marked failed, and you can still resend it by hand from the delivery log. Because a delivery can be retried, the same event can reach you more than once. Store the webhook-id of each event you've processed and skip ones you've seen. Every retry and every manual resend uses the same id.

Automatic disabling

If an endpoint fails every scheduled delivery for five days in a row, Tesselith switches it off, marks its pending deliveries failed, and notifies and emails the workspace's owners and admins. A single success at any point resets the clock. Turn it back on from its settings once it's fixed. Test sends and manual resends never count toward disabling.

Secrets and rotation

Each endpoint has its own secret, shown when it's created and revealable later in its settings. To rotate, click Rotate secret. For the next 24 hours every request is signed with both the old and new secrets (two values in webhook-signature), so your handler keeps verifying while you deploy the new secret. After 24 hours only the new secret signs.

Testing and debugging

  • Send test event delivers a signed webhook.test request right away and shows the HTTP status and response inline. It's the fastest way to check your verification code.
  • The delivery log keeps every delivery for 30 days: its status (pending, succeeded, failed), each attempt's status code, duration and error, plus the request body and the first 4 KB of your response. Filter by event or status.
  • Resend replays any delivery immediately with the same webhook-id, so your deduplication still works.
  • For local development, expose your machine with a tunnel and use its public HTTPS URL. Tesselith refuses private and loopback addresses even at send time, so a plain localhost URL won't work.

Managing endpoints with the API

Keys with the webhook:manage scope can manage endpoints in code. GET /v1/webhooks lists endpoints, POST /v1/webhooks creates one and returns its secret once, and DELETE /v1/webhooks/{id} removes one (its pending deliveries are dropped). A workspace can have up to 20 endpoints.

curl -s -X POST https://api.tesselith.com/v1/webhooks \
  -H "Authorization: Bearer $TESSELITH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/tesselith",
       "description":"Slack alerts",
       "event_types":["post.published","post.failed","channel.reconnect_required"]}'

Recipes

  • Alert on failures: forward post.failed and channel.reconnect_required to a Slack or Teams channel so nobody learns about a dead account from a missing post.
  • Close the loop in a CMS: when post.published arrives for a post your CMS created, store the post id against the article and mark it as shared.
  • Keep a warehouse in sync: on post.published, call GET /v1/analytics/posts/{post_id} a day later and load the numbers into your reporting.
  • Watch the queue: on queue.updated, check that each channel still has posts lined up for the coming week and nudge the team if one runs dry.

Questions developers ask

Why didn't my webhook arrive?
Open the endpoint in Settings → Developers → Webhooks and check the delivery log. It shows every attempt with the status code or error. The common causes are a URL that isn't public HTTPS, a handler that takes longer than 15 seconds, or verification failing because the body was parsed before verifying.
Are webhook events delivered in order?
Usually, but not guaranteed. A retry can arrive after a later event. Use the payload's timestamp and fetch the object's current state from the API rather than relying on order.
Which IP addresses do webhooks come from?
Don't allowlist by IP. Verify the signature instead: it proves the request came from Tesselith and wasn't modified, wherever it was sent from.
Can I receive webhooks for only one channel?
Endpoints subscribe by event type for the whole workspace. Filter by channel in your handler, using the channel ids on the post you fetch.

Build on Tesselith

Create a workspace, connect your channels and generate an API key in Settings → Developers.

Join the waitlist

Related pages

API getting startedAPI error codesWebhooks overviewSocial media webhooks explained

More docs

Tesselith API: Getting Started GuideAPI Error CodesConnect Claude and Other AI Assistants to Tesselith (MCP)

Opening soon

Be first in when the doors open.

Join the waitlist and you'll be in the first wave of invites. One email when it opens, nothing else.

Join the waitlist
Tesselith

The social scheduler for creators, teams and agencies. One calendar, every network you post to.

Product

  • Composer
  • Calendar
  • Analytics
  • Automation
  • AI assistant
  • Posting queue
  • API & webhooks
  • MCP server
  • All features

Solutions

  • Creators
  • Agencies
  • Small business
  • E-commerce
  • All solutions

Compare

  • vs Buffer
  • vs Hootsuite
  • vs Later
  • vs Sprout Social
  • All comparisons
  • Alternatives

Free tools

  • Character counter
  • Best time to post
  • UTM builder
  • Engagement rate
  • All tools

Company

  • About
  • Blog
  • Developer docs
  • Pricing
  • Contact
  • Security
  • Status

Legal

  • Privacy
  • Terms
  • Cookies
  • Acceptable Use
  • DPA
  • Refunds

Schedule posts for

  • X (Twitter)
  • Instagram
  • Facebook Pages
  • LinkedIn
  • YouTube
  • TikTok
  • Threads
  • Bluesky
  • Mastodon
  • Pinterest
  • Reddit
  • Discord
  • Telegram
  • Dev.to
  • Hashnode
© 2026 Tesselith. All rights reserved.
PrivacyTermsCookiesSecurityData deletion
Tesselith