antworkDOCS

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/webhooks
Request
curl 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"] }'
201 Created
{
  "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, con consecutiveFailures, lastStatus y lastDeliveryAt.
  • PATCH /v1/webhooks/{id} cambia url, events, description o status (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}/test envía un evento ping firmado.
  • GET /v1/webhooks/{id}/deliveries devuelve los últimos 50 intentos, guardados 30 días.
  • POST /v1/webhooks/{id}/rotate-secret devuelve 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ó; publishedUrl enlaza a ella.
  • post.failed — una publicación no saldrá sin ti; error.message y error.advice dicen 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.
POST to your URL
{
  "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-signature

Los 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.

Node
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}`)
}
Python
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())
Rechaza un webhook-timestamp de hace más de cinco minutos, para que un envío capturado no pueda repetirse.

Reintentos y desactivación

  • Responde cualquier 2xx en 10 segundos. Haz el trabajo después de responder.
  • Un tiempo agotado, 408, 429 o 5xx se 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 status en active.
  • Un reintento lleva el mismo webhook-id; úsalo para ignorar repeticiones. Los eventos pueden llegar desordenados.

¿Algo no cuadra? Dinos cuál.