Ir al contenido

Webhooks

Una URL HTTPS por workspace. Sin events recibes todos. El secreto de firma solo se devuelve al crearlo: guárdalo.

Ventana de terminal
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"
]
}'
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).

Cuerpo
{
"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/…" }
]
}
}
}

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.

Node
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));
};
Python
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"])
  • Responde con un 2xx en 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-Id para no procesarlo dos veces.

© 2026 Marsial by TWOFACES ·marsial.dev ·Panel ·soporte@twofacesco.com