Webhooks
Recibe en tu servidor un evento cuando una publicación se programa, se publica o falla, o cuando una cuenta necesita reconectarse, en vez de consultarlo.
Registrar un 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_..."
}Requiere una clave con el ámbito write. workspaceId es obligatorio si perteneces a varios espacios; un webhook pertenece a uno. El secret firma cada envío y solo se muestra una vez. Como máximo 5 webhooks por espacio. Los mismos webhooks están en la página Desarrollador de la app.
GET /v1/webhooks?workspaceId=los lista, conconsecutiveFailures,lastStatusylastDeliveryAt.PATCH /v1/webhooks/{id}cambiaurl,events,descriptionostatus(active|disabled).DELETE /v1/webhooks/{id}lo elimina. Zapier y n8n se suscriben y se dan de baja con este par.POST /v1/webhooks/{id}/testenvía un eventopingfirmado.GET /v1/webhooks/{id}/deliveriesdevuelve los últimos 50 intentos, guardados 30 días.POST /v1/webhooks/{id}/rotate-secretdevuelve un secreto nuevo; el anterior deja de firmar al momento.
Eventos
post.scheduled— una publicación se programó para más tarde (una publicación inmediata solo envía su resultado).post.published— una publicación salió;publishedUrlenlaza a ella.post.failed— una publicación no saldrá sin ti;error.messageyerror.advicedicen por qué. Se envía una vez, tras los reintentos de Antwork, no por intento.account.needs_reconnect— la conexión de una cuenta social se rompió; sus publicaciones fallan hasta que se reconecte.account.disconnected— alguien desconectó una cuenta social en 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 es la publicación tal como la devuelve GET /v1/posts/{id}, y data.account la cuenta como GET /v1/accounts/{id}, leídas en el momento del evento.
Verificar la firma
webhook-signatureLos envíos siguen la especificación Standard Webhooks, así que cualquiera de sus librerías los verifica. La firma es HMAC-SHA256, con la clave en base64 que sigue a whsec_, sobre webhook-id.webhook-timestamp.cuerpo, enviada como v1,<base64> en webhook-signature. Usa el cuerpo crudo de la petición, antes de parsear el JSON.
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 de hace más de cinco minutos, para que un envío capturado no pueda repetirse.Reintentos y desactivación
- Responde cualquier
2xxen 10 segundos. Haz el trabajo después de responder. - Un tiempo agotado,
408,429o5xxse reintenta con espera creciente de 30 segundos a una hora, 8 intentos en total, alrededor de una hora. - Cualquier otra respuesta, una redirección incluida, termina el evento al momento: las redirecciones no se siguen.
- Tras 20 eventos seguidos sin entregar, el webhook se desactiva. Arregla el receptor y vuelve a poner
statusenactive. - Un reintento lleva el mismo
webhook-id; úsalo para ignorar repeticiones. Los eventos pueden llegar desordenados.
¿Algo no cuadra? Dinos cuál.