Webhooks
Get an event POSTed to your server when a post is scheduled, published or fails, or when an account needs reconnecting, instead of polling for it.
Register an endpoint
POST /v1/webhookscurl https://antwork.io/api/v1/webhooks \
-H "Authorization: Bearer ak_..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/antwork", "events": ["post.published", "post.failed"] }'{
"object": "webhook",
"id": "Hk2...",
"workspaceId": "ws_...",
"url": "https://example.com/antwork",
"events": ["post.published", "post.failed"],
"status": "active",
"secret": "whsec_..."
}Needs a key with the write scope. workspaceId is required when you belong to several workspaces; a webhook belongs to one. The secret signs every delivery and is shown once. At most 5 webhooks per workspace. The same webhooks are on the Developer page in the app.
GET /v1/webhooks?workspaceId=lists them, withconsecutiveFailures,lastStatusandlastDeliveryAt.PATCH /v1/webhooks/{id}changesurl,events,description, orstatus(active|disabled).DELETE /v1/webhooks/{id}removes it. Zapier and n8n subscribe and unsubscribe with this pair.POST /v1/webhooks/{id}/testsends a signedpingevent.GET /v1/webhooks/{id}/deliveriesreturns the last 50 attempts, kept 30 days.POST /v1/webhooks/{id}/rotate-secretreturns a new secret; the old one stops signing at once.
Events
post.scheduled— a post was scheduled for later (a publish-now sends only its outcome).post.published— a post went out;publishedUrllinks to it.post.failed— a post will not publish without you;error.messageanderror.advicesay why. Sent once, after Antwork's own retries, not per attempt.account.needs_reconnect— a social account's connection broke; posts on it fail until it is reconnected.account.disconnected— someone disconnected a social account in Antwork.
{
"id": "evt_8f3a...",
"type": "post.published",
"createdAt": "2026-11-03T10:00:04.512Z",
"workspaceId": "ws_...",
"data": {
"post": { "object": "post", "id": "pQ7x...", "status": "published", "publishedUrl": "https://...", ... }
}
}data.post is the post exactly as GET /v1/posts/{id} returns it, and data.account the account as GET /v1/accounts/{id} does, read the moment the event happened.
Verify the signature
webhook-signatureDeliveries follow the Standard Webhooks spec, so any of its libraries verifies them. The signature is HMAC-SHA256, keyed with the base64-decoded secret after whsec_, over webhook-id.webhook-timestamp.body, sent as v1,<base64> in webhook-signature. Use the raw request body, before any JSON parsing.
import crypto from 'node:crypto'
function verify(rawBody, headers, secret) {
const id = headers['webhook-id']
const ts = headers['webhook-timestamp']
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64')
const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64')
return headers['webhook-signature'].split(' ').some((s) => s === `v1,${expected}`)
}import base64, hashlib, hmac, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
if abs(time.time() - int(ts)) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
mac = hmac.new(key, f"{msg_id}.{ts}.".encode() + raw_body, hashlib.sha256).digest()
expected = "v1," + base64.b64encode(mac).decode()
return any(hmac.compare_digest(s, expected) for s in headers["webhook-signature"].split())webhook-timestamp more than five minutes old, so a captured delivery cannot be replayed.Retries and switching off
- Answer any
2xxwithin 10 seconds. Do the work after answering. - A timeout,
408,429or5xxis retried with backoff from 30 seconds to an hour, 8 attempts in all, about an hour. - Any other answer, a redirect included, ends the event at once: redirects are never followed.
- After 20 events in a row end undelivered, the webhook is switched off. Fix the receiver and set
statusback toactive. - A retry carries the same
webhook-id; use it to ignore a repeat. Events can arrive out of order.
Something look wrong? Tell us which.