Webhooks

Set up webhooks

Receive an HTTPS POST from Pozy when a post publishes, a message arrives or an account disconnects. Create an endpoint, verify it, and handle your first event.

Webhooks tell your app when something happens instead of making you ask. Pozy sends an HTTPS POST with a JSON body to your URL each time an event you subscribed to occurs: a post published, a DM arrived, a review was left, an account needs reconnecting. This page takes you from nothing to a verified endpoint receiving events.

#How it works

  1. You run an HTTPS endpoint that accepts POST requests.
  2. You register it with Pozy, with a secret and the events you want.
  3. Pozy signs each delivery with your secret and sends it. Your endpoint checks the signature, stores the event and answers 2xx within 5 seconds.
  4. If it does not answer 2xx in time, Pozy tries again, up to 7 attempts in total. See Retries.

#Build a receiver

Your handler needs the raw request body to verify the signature, so read the bytes before any JSON parsing.

// npm install express
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.POZY_WEBHOOK_SECRET!;
const queue: { id?: string; type: string; payload: unknown }[] = []; // stand-in for your job queue

app.post("/webhooks/pozy", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.get("X-Pozy-Signature") ?? "";
  const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
  const valid = signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!valid) return res.status(401).send("bad signature");

  const event = JSON.parse(req.body.toString("utf8"));
  // Store it and hand it to a background job; answer quickly.
  queue.push({ id: req.get("X-Pozy-Event-Id"), type: event.event, payload: event });
  res.sendStatus(200);
});

app.listen(3001);

The Signatures page covers the check in detail, and Idempotency and ordering shows how to make the background job safe to run twice.

#Register the endpoint

Generate a long random secret, keep it in your server's environment, and create the endpoint with POST /v1/webhooks/settings:

Terminal
export POZY_WEBHOOK_SECRET="$(openssl rand -hex 32)"
curl -X POST https://api.pozy.ai/v1/webhooks/settings \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production",
    "url": "https://app.example.com/webhooks/pozy",
    "secret": "'"$POZY_WEBHOOK_SECRET"'",
    "events": ["post.published", "post.partial", "post.failed", "account.disconnected", "message.received"]
  }'
Response
{
  "success": true,
  "webhook": {
    "_id": "whk_4Tq9Lm2Vx8Rb6Nc1Pz7K",
    "name": "Production",
    "url": "https://app.example.com/webhooks/pozy",
    "events": ["post.published", "post.partial", "post.failed", "account.disconnected", "message.received"],
    "isActive": true,
    "profileIds": [],
    "accountIds": [],
    "customHeaders": {},
    "failureCount": 0,
    "lastFiredAt": null
  }
}
Always set a secret

Pozy signs deliveries with the secret you choose and never generates one for you. An endpoint without a secret receives unsigned requests that anyone who knows the URL could forge.

Then send a test event to check the whole path: POST /v1/webhooks/test with the webhookId. See Test webhooks.

#Endpoint settings

Field Required What it does
name Yes A label for you, 1 to 50 characters.
url Yes Where deliveries go. Use HTTPS in production. Redirects are not followed, so give the final URL.
events Yes At least one event name. Unknown names are rejected with code: "invalid_event". See the events reference.
secret No, but set it The key for the X-Pozy-Signature HMAC.
profileIds No Only send events that belong to these profiles. Empty means every profile.
accountIds No Only send events about these connected accounts. Empty means every account.
customHeaders No Extra headers added to every delivery, for example a static token your gateway expects: { "X-Gateway-Token": "..." }.
isActive No false pauses deliveries without deleting the endpoint. Defaults to true.

A workspace can have up to 50 endpoints. Use several when different services care about different events, for example one for publishing results and one for the inbox.

#Send events to the right place

By default an endpoint receives matching events for the whole workspace. Two filters narrow it:

  • profileIds keeps events whose profile is in the list. A post event matches when any account it targets belongs to one of those profiles.
  • accountIds keeps events about one of those connected accounts.

With both set, an event must match both. Events that belong to no profile (workspace-level events, for example) only reach endpoints with no profileIds filter.

A common use is staging: put your test accounts in their own profile and point a staging endpoint at it with profileIds. Prefer profile filters to account filters, because a profile id stays the same when its account is reconnected.

If you run a platform with one profile per customer, every event also carries profiles[].externalId, your own customer id, so one endpoint can route to every tenant without a lookup. See Route webhooks to tenants.

#What a delivery looks like

Request
POST /webhooks/pozy HTTP/1.1
Host: app.example.com
Content-Type: application/json
User-Agent: Pozy-Webhooks/1.0
X-Pozy-Event: post.published
X-Pozy-Event-Id: 8e0b6f8a-3c51-4b7e-9d0a-2f4c6e1b9a77
X-Pozy-Delivery-Id: dlv_Qm3Zt8Kp1Wx6Bn4Rc9Ly
X-Pozy-Attempt: 1
X-Pozy-Signature: 3f6c1d0b9a7e5c2f4d8b6a1e0c9f7d5b3a2e4c6f8d0b1a3c5e7f9d2b4a6c8e0f
Body
{
  "id": "8e0b6f8a-3c51-4b7e-9d0a-2f4c6e1b9a77",
  "event": "post.published",
  "timestamp": "2026-11-03T08:30:04.000Z",
  "profiles": [{ "id": "670f9b1d4c2e8a7f3b6d5e10", "externalId": "cust_4821" }],
  "post": {
    "id": "6721a8c04e9b2f1d7c3a5e88",
    "content": "Scheduled exports are live.",
    "status": "published",
    "scheduledFor": "2026-11-03T08:30:00.000Z",
    "publishedAt": "2026-11-03T08:30:03.000Z",
    "platforms": [
      {
        "platform": "linkedin",
        "status": "published",
        "accountId": "6710c3e2a9f14b0d8e5c7a21",
        "platformPostId": "urn:li:share:7259012345678901234",
        "publishedUrl": "https://www.linkedin.com/feed/update/urn:li:share:7259012345678901234"
      }
    ],
    "metadata": { "campaignId": "cmp_2041" }
  }
}
Header Meaning
X-Pozy-Event The event name, same as event in the body.
X-Pozy-Event-Id The event's id, same as id in the body. Identical on every retry and redelivery, so use it to deduplicate.
X-Pozy-Delivery-Id This delivery. A redelivery of the same event gets a new delivery id.
X-Pozy-Attempt Which attempt this is, from 1 to 7.
X-Pozy-Signature Hex HMAC-SHA256 of the raw body, keyed with your secret. Sent when the endpoint has a secret.

Every body has id, event and timestamp. Events that belong to a profile add profiles, and the rest of the body depends on the event: post, account, message, comment and so on. Test deliveries add "test": true.

#Answer fast

Pozy waits 5 seconds for a response. Do the minimum inside the request: check the signature, write the event to a table or queue, answer 200. Do the real work (updating your database, calling other services, sending email) in a background job. An endpoint that does slow work inline will time out, get retried, and process the same event several times.

Any 2xx status counts as delivered. The body of your response is ignored, but the first 2,000 characters are kept in the delivery logs to help you debug.

#Manage endpoints

Task Call
List endpoints, with their secrets and failure counts GET /v1/webhooks/settings
Change events, URL, filters or secret PUT /v1/webhooks/settings with webhookId and the fields to change
Pause or resume PUT /v1/webhooks/settings with isActive
Delete DELETE /v1/webhooks/settings?webhookId=whk_...

Managing endpoints needs a workspace owner or admin in the dashboard, or an API key that has not disabled the webhooks resource group.

#Endpoints

Endpoint What it does
GET /v1/webhooks/settings List webhooks
POST /v1/webhooks/settings Create webhook
PUT /v1/webhooks/settings Update webhook
DELETE /v1/webhooks/settings Delete webhook
GET /v1/webhooks/logs List webhook delivery logs
POST /v1/webhooks/logs/redeliver Redeliver a webhook event
POST /v1/webhooks/test Send test webhook