Webhooks
Configura tu endpoint
Sección titulada «Configura tu endpoint»Una URL HTTPS por workspace. Sin events recibes todos. El secreto de firma solo se devuelve al crearlo: guárdalo.
curl -X PUT "https://api.marsial.dev/v1/webhook-endpoint" \ -H "Authorization: Bearer $MARSIAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://app.acme.com/webhooks/marsial", "events": [ "post.published", "post.failed", "account.disconnected" ] }'const res = await fetch('https://api.marsial.dev/v1/webhook-endpoint', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.MARSIAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "url": "https://app.acme.com/webhooks/marsial", "events": [ "post.published", "post.failed", "account.disconnected" ] }),});const { data } = await res.json();import os, requests
res = requests.put( "https://api.marsial.dev/v1/webhook-endpoint", headers={"Authorization": f"Bearer {os.environ['MARSIAL_API_KEY']}"}, json={ "url": "https://app.acme.com/webhooks/marsial", "events": [ "post.published", "post.failed", "account.disconnected" ] },)data = res.json()["data"]Eventos
Sección titulada «Eventos»| Evento | Cuándo | data |
|---|---|---|
post.scheduled |
Se programó o entró en la cola | post |
post.published |
Salió en todas las redes | post |
post.partial |
Salió en algunas y falló en otras | post |
post.failed |
No salió en ninguna, también cuando falla una red sola (una imagen que no descarga) | post |
post.cancelled |
Se canceló | post |
post.removed |
Se retiró de una red (también al borrarla con copias publicadas, antes de post.deleted) |
post |
post.rejected |
Se rechazó en la revisión y volvió a borrador | post |
post.recycled |
Se creó una copia reciclada | post (la copia), recycledFrom |
post.draft_saved |
Se guardó como borrador | post |
post.deleted |
Se eliminó de Marsial | post (como estaba) |
account.connected |
Se conectó una cuenta | account |
account.disconnected |
Se desconectó o hay que reconectarla | account |
message.received |
Alguien escribió un mensaje directo | accountId, conversationId, message |
comment.received |
Alguien comentó una publicación | accountId, postId, comment |
review.received |
Llegó una reseña nueva (Google Business) | accountId, review |
Para probar tu endpoint sin esperar a un evento real, POST /v1/webhook-endpoint/test con { "type": "post.failed" } (o cualquier otro tipo al que esté suscrito) envía un ejemplo firmado con test: true e ids inventados.
Cada estado de una publicación se avisa una vez: Marsial manda post.scheduled o post.published en cuanto lo sabe, aunque la red no diga nada, y no lo repite. Las entregas pueden cruzarse: ordénalas por createdAt (al borrar, post.deleted va un milisegundo después de post.removed).
{ "id": "evt_9a8s7d", "type": "post.published", "createdAt": "2026-10-23T10:00:05.000Z", "data": { "post": { "id": "pst_7q1w0e", "status": "published", "targets": [ { "accountId": "acc_3k9x2m", "platform": "instagram", "status": "published", "url": "https://www.instagram.com/p/…" } ] } }}Verifica la firma
Sección titulada «Verifica la firma»Cada envío lleva Marsial-Event, Marsial-Event-Id y Marsial-Signature: t=<segundos>,v1=<hex>. v1 es el HMAC-SHA256 de <t>.<cuerpo> con tu secreto. Compáralo con el cuerpo sin parsear y rechaza firmas de hace más de 5 minutos.
import { createHmac, timingSafeEqual } from 'node:crypto';
export const verifyMarsialSignature = (rawBody, header, secret) => { const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('='))); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));};import hmac, hashlib, time
def verify_marsial_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(part.split("=", 1) for part in header.split(",")) if abs(time.time() - int(parts["t"])) > 300: return False expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"])Reintentos
Sección titulada «Reintentos»- Responde con un
2xxen menos de 10 segundos. Haz el trabajo pesado después. - Si fallas, reintentamos varias veces con espera creciente.
- Un mismo evento puede llegar más de una vez: usa
Marsial-Event-Idpara no procesarlo dos veces.
© 2026 Marsial by TWOFACES ·marsial.dev ·Panel ·soporte@twofacesco.com