Webhooks
Reelwire crea el video. Cuando una publicación vence, se entrega, por correo electrónico, por webhook o por ambos, según indique cada uno de sus canales, y quien la recibe la publica: su propio script, una automatización, un servicio de publicación. (Un canal cuyo conector de plataforma está activado también lo publica el propio Reelwire: consulte Canales.) Esta página es la mitad del webhook: una llamada firmada a una dirección que usted define, que lleva el video, su imagen de portada y el texto de cada canal de la entrega, y la llamada que usted hace de vuelta cuando la publicación de un canal está publicada. Los cambios y las retiradas llegan a la misma dirección. Una dirección atiende a todo el espacio de trabajo: usted ejecuta un receptor, no uno por cuenta.
El capítulo Guías recorre los tres receptores habituales, desde la primera solicitud hasta el informe de vuelta: Entrega por webhook.
| Método | Ruta | Permiso |
|---|---|---|
GET | /v1/webhook | webhooks:read |
PUT | /v1/webhook | webhooks:write |
POST | /v1/webhook/secret | persona con sesión iniciada |
POST | /v1/webhook/secret/reveal | persona con sesión iniciada |
POST | /v1/webhook/test | webhooks:write |
POST | /v1/posts/{postId}/report | posts:write |
GET | /v1/deliveries | connections:read |
GET | /v1/deliveries/{id} | connections:read |
POST | /v1/deliveries/{id}/redeliver | connections:write |
Adónde van las entregas es configuración, y una clave puede leerla y establecerla. Rotar el secreto de firma entrega un secreto nuevo, y mostrarlo de nuevo entrega el actual, así que, como crear una clave de API, ambos corresponden a una persona con sesión iniciada y el permiso de Administración: una clave es rechazada tenga lo que tenga. Un asistente de IA conectado iniciando sesión tampoco tiene nunca webhooks:write, haya permitido lo que haya permitido la persona: puede leer adónde van las entregas, pero establecer la dirección o enviar una entrega de prueba requiere una clave creada a mano en la pantalla Claves de API, o una persona. Consulte La conexión de un asistente.
Han cambiado cuatro cosas, y un receptor escrito para la forma anterior necesita las cuatro:
- Una llamada lleva todos los canales de una entrega, en
channels, cada uno con su propiopostId,mediaytext. La llamada anterior llevaba un canal, conchannel,media,textycontexten el nivel superior. - Su respuesta ya no se lee. Antes llevaba
platformPostIdypermalink, y se tomaba como la publicación realizada. Ahora la publicación de un canal está publicada cuando usted lo dice con la llamada de informe de vuelta. - Todo evento va a la única dirección que usted define. Una actualización y una eliminación ya no van a la dirección con su
/publishcambiado por la acción. post.recallha desaparecido. Estaba declarado y nunca se envió.
Adónde van las entregas
curl http://localhost:4000/v1/webhook \
-H "Authorization: Bearer rw_your_key_here"
{
"id": "01M1VFRWE7P5V056GE36CXEK5D",
"url": "https://hooks.example.com/reelwire",
"enabled": true,
"secretHint": "whse...kSf8",
"signatureHeader": "x-reelwire-signature",
"signatureFormat": "t=<unix>,v1=<hex hmac-sha256 of `${t}.${rawBody}`>"
}
PUT /v1/webhook establece la dirección, con { "url": "https://..." }, y responde con el id y la url tal como se guardó. Una clave necesita webhooks:write; una persona con sesión iniciada necesita el permiso de Administración, como para Probar la entrega y Entregar de nuevo. Debe ser HTTPS, no llevar nombre de usuario ni contraseña, tener como máximo 2048 caracteres sin espacios ni saltos de línea y resolverse a una dirección pública. Esto último se comprueba contra aquello a lo que se resuelve el host antes de cada entrega, no solo al guardar.
POST /v1/webhook/secret, con un token de sesión, rota el secreto de firma y lo devuelve una vez como secret, exactamente como se devuelve una vez una clave de API. No se notifica a nadie: el receptor es un tercero y hay que darle el valor, que es el paso que realiza una integración real y el que ocultaría un secreto enviado por la fuerza. Envíe { "secret": "..." } para establecer un valor que ya tiene, de 24 a 200 caracteres, o envíe {} para que se genere uno.
POST /v1/webhook/secret/reveal, con un token de sesión y { "password": "..." } (la contraseña con la que la persona inicia sesión), devuelve de nuevo el secreto actual como secret, como hace mostrar de nuevo una clave de API. Necesita el permiso de Administración, está sujeto al límite de frecuencia del inicio de sesión, responde 403 ante una contraseña incorrecta y se escribe en el registro de auditoría. Ninguna clave de API puede llamarlo. En la pantalla Webhook estos dos son Copiar el secreto y Rotar el secreto, uno al lado del otro.
Entrega de prueba
POST /v1/webhook/test envía ahora un ping firmado a la dirección, mientras espera, y responde con lo que volvió:
curl -X POST http://localhost:4000/v1/webhook/test \
-H "Authorization: Bearer rw_your_key_here"
{
"ok": true,
"deliveryId": "01M3DF0R6K9W2E5T8Y1B4N7M3Q",
"url": "https://hooks.example.com/reelwire",
"status": 200,
"durationMs": 184,
"response": "{\"received\":true}",
"error": null
}
Llega exactamente como una entrega, con los mismos tres encabezados, firmado del mismo modo y bajo la misma regla sobre direcciones públicas, así que es la forma de comprobar su firma y el manejo de repeticiones antes de que le llegue una publicación real. Se envía una vez, nunca se reintenta, y espera 15 segundos una respuesta.
Cuando no llega, la respuesta sigue siendo un 200, con ok false y error que indica por qué: «The receiver answered 401: ...» donde su receptor respondió, o lo que fue mal donde nadie respondió. Sin dirección definida todavía, es un 409. Cada prueba se escribe en el registro de entregas como una línea propia, sobre ninguna publicación. En la pantalla Webhook es el botón Probar la entrega junto a la dirección, y para un asistente de IA es reelwire_test_webhook, que necesita una clave con webhooks:write (la conexión por inicio de sesión de un asistente nunca la tiene, véase arriba).
Qué canales llegan aquí
Solo los canales con Entrega por webhook activada (deliverByWebhook). Un canal solo con Correo nunca llega a su dirección: sus entregas son correos a las personas que publican por el espacio de trabajo (consulte Publicaciones entregadas por correo electrónico). Un canal con ambas activadas se entrega por ambas vías a la vez, y el correo y su llamada comparten un handoverId. Un canal con todas las vías desactivadas no llega a nadie del propio espacio de trabajo.
Un canal con Entrega por conector de plataforma activada además también lo publica el propio Reelwire, y nada en su llamada lo dice: un receptor que publica todos los canales que se le dan publicaría ese una segunda vez.
El formato de un canal no decide nada sobre cómo se entrega. Decide la forma del clip y el texto escrito para él. Consulte Cómo se entrega una publicación.
Una llamada por entrega
Una publicación es un contenido en un espacio de trabajo: todo lo que produce un elemento de feed, un evento de un feed propio o una publicación de una lista de publicaciones, para cada canal que lo recibe. Cada canal sigue teniendo su propio render, en su propio formato e idioma. postKey nombra la publicación y es igual en cada entrega de ella. Una republicación es una publicación propia, con un postKey propio.
Una publicación se entrega por lotes, no canal a canal:
- Los canales que no necesitan aprobación van juntos, en una llamada, cuando todos están renderizados y vencidos. Uno que aún se renderiza retiene a los demás como máximo 30 minutos; después van los demás, y él sigue por su cuenta, en una llamada propia.
- Un canal que necesita aprobación va solo, una vez aprobado. Nunca retiene a los demás, y ellos nunca lo esperan.
- Un canal que no llega ahora se omite y se nombra. Su render falló, se canceló o se rechazó, o su canal está en pausa o eliminado.
omittedlista cada canal de la publicación que una llamada deja fuera, con el motivo en palabras, yfollowsindica si aún llegará por su cuenta: espera aprobación, aún se renderiza, aún no vence, o su canal está en pausa y llega cuando se reanude el canal. - No se entrega nada antes de que venza.
Así que una publicación para cuatro canales, uno de los cuales pide aprobación, llega como dos llamadas: tres canales juntos, y el cuarto cuando alguien lo ha aprobado. Ambas llevan el mismo postKey.
Una dirección para cada evento
event | Cuándo llega |
|---|---|
post.publish | Una publicación vence: los canales de un lote, para publicar. |
post.update | Alguien cambió una publicación ya entregada: su texto, y quizá su video. |
post.delete | Alguien pidió retirar una publicación ya entregada. |
ping | Una entrega de prueba. No es sobre ninguna publicación. |
Todos van a la única dirección que usted define. x-reelwire-event y el event del cuerpo los distinguen, así que enrute según ellos, nunca según la ruta.
Una actualización y una eliminación van por las vías que el canal de la publicación tiene activadas ahora (correo, webhook, conector de plataforma), o por las de ellas que nombre la solicitud en ways, y nunca por una vía desactivada: nombrar una es un 409. Consulte Actualizar una publicación ya entregada y Eliminar.
Un post.publish también puede llegar de nuevo para una publicación que ya tenía: Reenviar solicitud de publicación en la pantalla Publicaciones con Webhook elegido, POST /v1/jobs/{id}/resend-webhook, lo envía construido tal como se lee ahora la publicación, con un deliveryId y un handoverId nuevos, el mismo postKey y los mismos postIds y un omitted vacío, de modo que un receptor que conserva los ids de entrega que procesó actúa de nuevo sobre él. Una publicación que falló y se reintenta, POST /v1/jobs/{id}/retry (Reenviar solicitud de publicación en una publicación fallida), llega del mismo modo: un post.publish nuevo con ids nuevos, tal como se lee ahora la publicación. Consulte Reintentar una publicación fallida.
Encabezados en cada entrega
| Encabezado | Valor |
|---|---|
content-type | application/json; charset=utf-8 |
x-reelwire-signature | t=<unix seconds>,v1=<hex hmac-sha256> |
x-reelwire-delivery | El id de entrega, un ULID. El mismo en cada reintento de una entrega. |
x-reelwire-event | post.publish, post.update, post.delete o ping |
El juego de caracteres se declara explícitamente porque un receptor que lo deduce puede decodificar UTF-8 como latin1, lo que convierte una raya larga de un texto en mojibake y, peor, cambia los bytes contra los que se comprueba la firma.
Verificar la firma
La cadena firmada es `${t}.${rawBody}`, HMAC-SHA256 con su secreto, codificado en hexadecimal. Es la construcción de Stripe, y por la misma razón: ligar la marca de tiempo a la cadena firmada es lo que da sentido a una comprobación de desfase. Sin ella, un cuerpo capturado sigue siendo válido para siempre.
Su secreto de firma es todo lo que necesita: cada espacio de trabajo tiene el suyo, así que una entrega que se verifica con el suyo es de su espacio de trabajo y de nadie más. Lea t y v1 del encabezado.
import { createHmac, timingSafeEqual } from "node:crypto";
// `raw` must be the exact bytes of the request body. Not a re-serialised object:
// JSON.stringify of a parsed body will not reproduce them.
export function verify(raw, header, secret) {
const parts = new Map(
header.split(",").map((chunk) => {
const at = chunk.indexOf("=");
return [chunk.slice(0, at).trim(), chunk.slice(at + 1).trim()];
}),
);
const t = Number(parts.get("t"));
const v1 = parts.get("v1");
// 300 seconds. Required, not optional: the signature alone does not stop a replay.
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${raw}`).digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(v1 ?? "", "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}
Dos comprobaciones, ambas obligatorias. Rechace una marca de tiempo a más de 300 segundos de su propio reloj, y rechace un x-reelwire-delivery que ya haya procesado. La firma por sí sola no impide una repetición.
El cuerpo se canonicaliza antes de firmarse: claves de objeto ordenadas, lo indefinido omitido, de modo que los bytes que recibe son exactamente los bytes que se firmaron. Cada intento se firma de nuevo: un reintento lleva el mismo id de entrega, con una marca de tiempo nueva, el siguiente attempt y sus enlaces de medios firmados de nuevo.
Qué lleva una publicación
Un post.publish para una publicación cuyo canal de Instagram Reels no necesita aprobación, mientras su canal de TikTok espera a un revisor:
{
"event": "post.publish",
"deliveryId": "01M3DE9BPKN1YNWG20WCN0MN0V",
"handoverId": "01M3DE9BPJR3NG04N6EKGRYBP3",
"postKey": "01M3DE6QP2QJEC5SSAFKS26RVN",
"occurredAt": "2026-09-25T23:26:55.187Z",
"attempt": 1,
"source": {
"kind": "queue",
"queueName": "Default post list",
"title": "Weekly market wrap"
},
"channels": [
{
"postId": "01M3DE97XZMTT6GX71JAS8Z9VJ",
"postGroupId": "01M3DE6QPKHYJYTX24PFCS8QD4",
"channelId": "01M3DE675G8Y9JPZCHN6VTKMHN",
"channelName": "My brand Reels DE",
"platform": "instagram",
"surface": "instagram-reels",
"ratio": "9x16",
"locale": "de",
"posting": {
"allowComments": true,
"brandedContent": false,
"allowDuetStitch": false,
"hashtagsAsFirstComment": false,
"silent": false,
"notifySubscribers": true,
"shareToFeed": true,
"protectContent": false
},
"brandName": "My brand",
"template": { "id": "tpl_dbadb7ef4a70", "version": "1.0.0" },
"approval": "none",
"media": [
{
"assetId": "01M3DE97W8E2Q36QC3B9DFZ0DF",
"kind": "video",
"ratio": "9x16",
"mimeType": "video/mp4",
"width": 1080,
"height": 1920,
"durationMs": 15000,
"byteSize": 6235839,
"sha256": "85c4152676d1b376c34624250a681904725d1d73c3bd7fa2f63dc79138ff8b53",
"url": "https://app.reelwire.io/v1/assets/01M3DE97W8E2Q36QC3B9DFZ0DF/download?exp=1790983615&sig=...",
"expiresAt": "2026-10-02T23:26:55.000Z"
},
{
"assetId": "01M3DE97X0XT1B6CP9K7R1CVXW",
"kind": "still",
"ratio": "9x16",
"mimeType": "image/jpeg",
"width": 540,
"height": 960,
"durationMs": 0,
"byteSize": 33524,
"sha256": "2a6735750c0e3f76f658baa2277a3b13e40c76afe12bbcdee6f5bb980b9751b6",
"url": "https://app.reelwire.io/v1/assets/01M3DE97X0XT1B6CP9K7R1CVXW/download?exp=1790983615&sig=...",
"expiresAt": "2026-10-02T23:26:55.000Z"
}
],
"text": {
"locale": "de",
"caption": "Die Märkte schließen die Woche höher. Was das für die kommenden Tage heißt, zeigt der Clip.",
"hashtags": ["beispiel", "reelwire", "mybrand"],
"disclaimer": "My brand shares short video updates on the latest news."
}
}
],
"omitted": [
{
"postGroupId": "01M3DE6QPN8V2KQ4T7X1ZC5RWA",
"channelId": "01M3DE675J3W9E6K1F0PS2BHTD",
"channelName": "My brand TikTok DE",
"reason": "It waits for approval, and follows on its own once approved.",
"follows": true
}
]
}

Las claves llegan ordenadas alfabéticamente, porque el cuerpo se canonicaliza; aquí se muestran en orden de lectura.
Puntos que conviene observar:
channelscontiene todos los canales del lote. Recórralo en un bucle: una publicación para tres canales sin revisor es una llamada con tres entradas.postIdes contra lo que usted informa de vuelta;postGroupIdes la publicación del canal en la pantalla Publicaciones, ychannelIdel canal.- No hay identificador de cuenta.
channelIdnombra el destino; el receptor lo asigna a su propia cuenta, que es la única asignación que puede ser correcta. Reelwire nunca ha tenido los nombres de sus cuentas, y un campo que solo podría rellenar con una suposición es peor que ningún campo.channelNamees el nombre que el espacio de trabajo dio al canal, una etiqueta para una persona que lee un registro. platformysurfaceson el formato del canal. Indican para qué formato y qué límites de texto se hizo el clip. No prometen nada sobre quién lo publica ni cómo.platformes uno deyoutube,facebook,instagram,tiktok,linkedin,telegram,discord,slack,threema,x,wechat,signal,pinterest,threads,whatsappodownload; los nueve posteriores atelegramse añadieron con los conectores de plataforma, así que un receptor que compare el valor con una lista debería admitirlos también.media[0]es el video ymedia[1]su imagen de portada, un JPEG con la misma forma, cuando el renderizador hizo una.sha256se comprueba después de la descarga, no se trata como una pista. Una discrepancia es un fallo grave.media[].urlse firma de nuevo en cada envío y funciona siete días desde él, de modo que un receptor que pone en cola una publicación para una hora de publicación propia aún encuentra el archivo. Un clip eliminado entretanto del almacenamiento del espacio de trabajo, para hacer sitio a otros más nuevos, responde 404 diga lo que diga el enlace, así que descárguelo pronto.textya está localizado y ajustado al formato del canal. El receptor no traduce.disclaimeres el texto propio de la marca, mantenido aparte del texto de la publicación para que pueda colocarlo después de las palabras propias de la publicación.titlellega donde la plataforma tiene un campo de título, ylinkUrlylinkLabelllevan el enlace de la marca donde el formato puede entregar uno.captionssolo llega para un canal en Sin plataforma específica (platformdownload), que no nombra adónde van sus clips. Contiene entonces el texto en cada longitud en que se escribió la publicación, hasta cuatro, una por tipo de plataforma, cada una con sulength, unalabelque la nombra y nombra las plataformas para las que está escrita, y su propiotext:tinypara Pinterest, Threads y X,shortpara Telegram, Threema, WeChat y WhatsApp,mediumpara Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack y TikTok,longpara Facebook Feed y YouTube. Se omite cuando la publicación tiene un solo texto, o cuando cada longitud se leería igual.tinyes nuevo desde la versión 0.76.0, así que un receptor que comparelengthcon una lista debería admitirlo también. Un canal que nombra su plataforma, como el de arriba, recibe el texto de esa plataforma entexty ningúncaptions.postingson los Ajustes de publicación del canal, con los valores predeterminados rellenados. Están todos los interruptores; los que su formato no tiene están siempre en sus valores predeterminados y no significan nada para él, yvisibilityllega solo donde el formato ofrece una opción y se ha elegido una (ausente es público). Consulte Ajustes de publicación para saber qué formato tiene cuáles. Le corresponde a usted aplicarlos al publicar; el conector de plataforma de un canal los aplica por sí mismo, y el kit de publicación los lista para quien publica a mano. Unpost.updatetambién los lleva, tal como están cuando se envía el cambio.notifySubscribers(YouTube, activado salvo que se desactive),shareToFeed(Instagram Reels, activado salvo que se desactive) yprotectContent(Telegram: nadie puede reenviar la publicación ni guardar el video) son nuevos desde la versión 0.77.0; en X, Threads y PinterestallowCommentses ahora propio del canal, cuando antes siempre estaba activado.templatees la compilación exacta que se renderizó, nunca un alias, convariant, el corte en que se renderizó el clip (el del canal o el propio de una publicación de una lista de publicaciones), donde lo hay.approvalesnoneoper_post_approved, conapprovedByyapprovedAtdonde lo aprobó un revisor.sourceindica de dónde vino el contenido:broadcast, un feed que incluye Reelwire, consourceSlugyformatSlug;stream, uno de sus feeds propios, constreamId; oqueue, una lista de publicaciones, conqueueNamey eltitlepropio de la publicación.handoverIdlo comparten el correo y la llamada de una entrega.attemptempieza en 1 y cuenta los envíos de esta entrega.syndicationnunca está en una llamada a su propio webhook. Está en una llamada al webhook de uno de sus participantes de sindicación,{ "partnerId", "from" }, que lleva el mismo documento recortado a los canales que eligieron, firmado con el secreto de su grupo, y en unpingde prueba que la página de su grupo envía a un webhook que ya tienen.
Una actualización, una eliminación y un ping
post.update
{
"event": "post.update",
"deliveryId": "01M3ECC1CXB2N6R0W3Y7C1E5K9",
"handoverId": "01M3ECC1CWXQ2M5P9S3V7Z1D4F",
"postKey": "01M3DE6QP2QJEC5SSAFKS26RVN",
"occurredAt": "2026-09-26T08:12:40.221Z",
"attempt": 1,
"channels": [
{
"postId": "01M3DE97XZMTT6GX71JAS8Z9VJ",
"postGroupId": "01M3DE6QPKHYJYTX24PFCS8QD4",
"channelId": "01M3DE675G8Y9JPZCHN6VTKMHN",
"channelName": "My brand Reels DE",
"platform": "instagram",
"surface": "instagram-reels",
"ratio": "9x16",
"locale": "de",
"platformPostId": "18034567890123456",
"permalink": "https://www.instagram.com/reel/DAb3xYz9QwE/",
"posting": {
"allowComments": true,
"brandedContent": false,
"allowDuetStitch": false,
"hashtagsAsFirstComment": false,
"silent": false,
"notifySubscribers": true,
"shareToFeed": true,
"protectContent": false
},
"text": {
"locale": "de",
"caption": "Die Märkte schließen die Woche höher, angeführt von den Technologiewerten.",
"hashtags": ["beispiel", "reelwire", "mybrand"],
"disclaimer": "My brand shares short video updates on the latest news."
}
}
]
}
text es el texto completo tal como debe leerse ahora, no una diferencia, y captions llega cuando el canal está en Sin plataforma específica. media solo está cuando se sustituyó el video: contiene entonces el nuevo video, un archivo de la biblioteca multimedia del espacio de trabajo, con assetId, kind video, mimeType, byteSize, sha256, url y expiresAt, firmado como el clip de una publicación. Sin él, conserve el video que tiene. platformPostId y permalink son lo que se informó de vuelta para ese canal, donde alguien lo hizo, para que pueda volver a encontrar la publicación. posting son los ajustes de publicación del canal tal como están ahora, como los lleva una publicación.
Reelwire no cambia nada donde está la publicación: lo hace su receptor. La publicación en Reelwire adopta el texto nuevo una vez que la llamada ha llegado.
post.delete
{
"event": "post.delete",
"deliveryId": "01M3H534R4C5F8J1M4Q7T0X3A6",
"handoverId": "01M3H534R3BR9V3Y6B0E4H7K1N",
"postKey": "01M3DE6QP2QJEC5SSAFKS26RVN",
"occurredAt": "2026-09-27T10:03:12.004Z",
"attempt": 1,
"channels": [
{
"postId": "01M3DE97XZMTT6GX71JAS8Z9VJ",
"postGroupId": "01M3DE6QPKHYJYTX24PFCS8QD4",
"channelId": "01M3DE675G8Y9JPZCHN6VTKMHN",
"channelName": "My brand Reels DE",
"platform": "instagram",
"surface": "instagram-reels",
"ratio": "9x16",
"locale": "de",
"platformPostId": "18034567890123456",
"permalink": "https://www.instagram.com/reel/DAb3xYz9QwE/"
}
]
}
Retire la publicación definitivamente, no la oculte, y después informe deleted. Hasta que alguien lo haga, el canal lee Eliminación solicitada en la pantalla Publicaciones.
ping
{
"event": "ping",
"deliveryId": "01M3DF0R6K9W2E5T8Y1B4N7M3Q",
"occurredAt": "2026-09-25T23:39:41.702Z",
"attempt": 1,
"message": "reelwire webhook test"
}
Qué responde usted
Cualquier 2xx es una entrega. El cuerpo se conserva para el registro de entregas y no se lee, así que esto basta:
{ "received": true }
Un 2xx dice que tiene la entrega, nada más. No publica nada y no marca nada como publicado: la publicación del canal sigue entregada en la pantalla Publicaciones hasta que informe de vuelta.
Cualquier otra cosa, una redirección incluida, es una entrega fallida y se reintenta. Una redirección nunca se sigue hasta donde apunta. Solo se entrega a direcciones públicas. Un 410 Gone es aquí un fallo como cualquier otro y no desactiva nada: esa respuesta solo quita el webhook de un participante de sindicación de su grupo.
Informar de vuelta
Si la publicación de un canal salió, lo dice usted por separado, por canal, cuando lo sepa:
curl http://localhost:4000/v1/posts/01M3DE97XZMTT6GX71JAS8Z9VJ/report \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"status":"published","url":"https://www.instagram.com/reel/DAb3xYz9QwE/","platformPostId":"18034567890123456"}'
{
"ok": true,
"postId": "01M3DE97XZMTT6GX71JAS8Z9VJ",
"postGroupId": "01M3DE6QPKHYJYTX24PFCS8QD4",
"state": "PUBLISHED",
"permalink": "https://www.instagram.com/reel/DAb3xYz9QwE/"
}
Necesita una clave de API con Posts write, posts:write. El id es channels[].postId de la entrega; también sirve el id de un canal de GET /v1/jobs.
| Campo | Qué es |
|---|---|
status | Obligatorio: published, skipped, failed o deleted. |
url | Con published: dónde está la publicación, una dirección http o https de hasta 2000 caracteres. |
platformPostId | Con published: el id propio de la plataforma para la publicación, hasta 200 caracteres. |
reason | Con failed: por qué no se pudo publicar, hasta 1000 caracteres, con palabras que una persona lea en la pantalla Publicaciones. |
status | Se acepta mientras la publicación está | Pasa a |
|---|---|---|
published | PUBLISHING, HANDED_OVER, SKIPPED o FAILED | PUBLISHED, con publishedAt establecido en este momento y el enlace en la pantalla Publicaciones |
skipped | HANDED_OVER | SKIPPED: entregada y deliberadamente no publicada |
failed | PUBLISHING o HANDED_OVER | FAILED, con el motivo en la pantalla Publicaciones |
deleted | HANDED_OVER o PUBLISHED | RECALLED, que la pantalla Publicaciones muestra como Eliminada |
published se acepta mientras la publicación sigue PUBLISHING porque un receptor puede publicarla e informar antes de haber respondido la llamada que la trajo; su respuesta no deshace el informe. Una publicación omitida o fallida todavía puede informarse como publicada más tarde.
Decir lo mismo de nuevo está bien: nada se mueve, y se conservan el enlace y el id más recientes. Todo lo que el estado de la publicación no permite es un 409 que nombra el estado: «This post is published, so it cannot be reported skipped.» Un status desconocido, o un enlace que no es una dirección web, es un 400, y un id que no es de una publicación del espacio de trabajo, un 404.
El enlace y el platformPostId que informe viajan con cada post.update y post.delete posteriores de ese canal, para que quien tenga la publicación pueda volver a encontrarla.
El mismo informe es Marcar como publicada, Omitir y Marcar como eliminada en la pantalla Publicaciones y en el kit de publicación del correo, y reelwire_report_post para un asistente de IA. Lo diga quien lo diga, las reglas son estas.
Reintentos
Siete intentos durante algo más de tres horas, y después la entrega está muerta:
| Intento | Espera antes de él |
|---|---|
| 1 | inmediato |
| 2 | 10 segundos |
| 3 | 1 minuto |
| 4 | 5 minutos |
| 5 | 15 minutos |
| 6 | 1 hora |
| 7 | 2 horas |
Cada llamada tiene 30 segundos para responder antes de contar como fallida. Cuando se acaba la escalera:
- Una publicación que no llegó por ninguna vía deja las publicaciones de sus canales en
FAILED, con el motivo en la pantalla Publicaciones. Donde también hay otra vía activada, el correo o el conector de plataforma, y salió o sigue en camino, se quedan donde las deja esa, y la pantalla Publicaciones indica qué fue mal con el webhook. - Una actualización o una eliminación deja la publicación exactamente como estaba e indica por qué, en lugar de fingir que cambió.
Una línea de correo sube por la misma escalera. La de un conector de plataforma también, con ocho minutos para cada intento, salvo que un rechazo que volvería a producirse cada vez, como un token incorrecto o un video demasiado grande para la plataforma, la detiene de inmediato.
GET /v1/overview lleva attention.deadDeliveries, que es el número sobre el que alertar.
Vigilar y reenviar
GET /v1/deliveries es el registro de entregas, lo más reciente primero: una línea por vía de salida de cada entrega, de modo que una entrega que fue por correo y por webhook son dos líneas. Filtre con state, mechanism (EMAIL, WEBHOOK o CONNECTOR) y event, busque con q (el nombre de un canal, el error, la dirección a la que fue una línea), acote a un período con since y until y ordene con sort (createdAt, el valor predeterminado, o updatedAt) y ascending. Se pagina como todo otro registro: page desde 1 y perPage hasta 200, 40 si se omite (limit se lee igual), y la respuesta indica total, cuántas coinciden, y all, cuántas contiene el período.
curl "http://localhost:4000/v1/deliveries?mechanism=WEBHOOK&perPage=1" \
-H "Authorization: Bearer rw_your_key_here"
{
"deliveries": [
{
"id": "01M3DE9BPKN1YNWG20WCN0MN0V",
"event": "post.publish",
"mechanism": "WEBHOOK",
"state": "DELIVERED",
"attempt": 1,
"nextAttemptAt": "2026-09-25T23:26:55.192Z",
"lastStatusCode": 200,
"lastError": null,
"note": null,
"deliveredAt": "2026-09-25T23:26:55.412Z",
"createdAt": "2026-09-25T23:26:55.193Z",
"handoverId": "01M3DE9BPJR3NG04N6EKGRYBP3",
"postId": "01M3DE97XZMTT6GX71JAS8Z9VJ",
"postIds": ["01M3DE97XZMTT6GX71JAS8Z9VJ"],
"channels": ["My brand Reels DE"],
"channelName": "My brand Reels DE",
"platform": "instagram"
}
],
"total": 12,
"all": 31,
"page": 1,
"perPage": 1,
"facets": {
"state": ["DEAD", "DELIVERED"],
"mechanism": ["EMAIL", "WEBHOOK"],
"event": ["ping", "post.publish"]
}
}
facets son los valores que el período realmente contiene, de modo que un filtro nunca ofrece una opción que vacíe la lista.
state recorre PENDING, DELIVERING, RETRYING, DELIVERED, DEAD. postIds son las publicaciones de los canales que llevaba la línea y channels los nombres de sus canales; postId y platform solo se rellenan en una línea que llevaba una. mechanism es EMAIL, WEBHOOK o CONNECTOR para una publicación que hizo el conector de plataforma de un canal: una línea por publicación, cuyo response contiene el id de la plataforma y el enlace a la publicación. note es lo que dijo un conector sobre una línea que salió igualmente, como una publicación de LinkedIn cuyos comentarios no pudo cerrar; es null en todos los demás casos.
GET /v1/deliveries/{id} responde con una línea completa:
{
"id": "01M3DE9BPKN1YNWG20WCN0MN0V",
"event": "post.publish",
"mechanism": "WEBHOOK",
"state": "DELIVERED",
"attempt": 1,
"nextAttemptAt": "2026-09-25T23:26:55.192Z",
"lastStatusCode": 200,
"lastError": null,
"deliveredAt": "2026-09-25T23:26:55.412Z",
"createdAt": "2026-09-25T23:26:55.193Z",
"postIds": ["01M3DE97XZMTT6GX71JAS8Z9VJ"],
"channels": ["My brand Reels DE"],
"to": "https://hooks.example.com/reelwire",
"handover": {
"id": "01M3DE9BPJR3NG04N6EKGRYBP3",
"postKey": "01M3DE6QP2QJEC5SSAFKS26RVN",
"event": "post.publish",
"postIds": ["01M3DE97XZMTT6GX71JAS8Z9VJ"],
"omitted": [
{
"postGroupId": "01M3DE6QPN8V2KQ4T7X1ZC5RWA",
"channelId": "01M3DE675J3W9E6K1F0PS2BHTD",
"channelName": "My brand TikTok DE",
"reason": "It waits for approval, and follows on its own once approved.",
"follows": true
}
],
"resend": false,
"createdAt": "2026-09-25T23:26:55.191Z"
},
"response": { "received": true }
}
También lleva payload, omitido arriba: el cuerpo tal como salió la última vez, con los enlaces con los que se firmó entonces. to es la dirección a la que fue la llamada, conservada en la línea porque el espacio de trabajo puede apuntar su webhook a otro sitio más tarde; para un correo indica a quién va el correo, y para una línea de conector con qué nombre publicó el conector de plataforma. resend es true para una entrega que alguien pidió de nuevo, como Reenviar solicitud de publicación en la pantalla Publicaciones. response es lo que volvió: el JSON del receptor, o raw con el principio de sus palabras.
POST /v1/deliveries/{id}/redeliver envía una línea de nuevo, entregada o no: una llamada de webhook con el mismo id de entrega y sus enlaces firmados de nuevo, un correo enviado de nuevo tal como era. El recuento de intentos vuelve a empezar. Una publicación cuyas vías habían muerto todas dejó las publicaciones de sus canales en FAILED; enviarla de nuevo las devuelve a entregadas, de modo que el éxito de la línea les llega. Una clave necesita connections:write; una persona con sesión iniciada necesita el permiso de Administración. Una línea de conector que llegó es un 409: una plataforma no conoce un id de entrega y aceptaría la publicación por segunda vez. En la pantalla Actividad es Entregar de nuevo en una línea de Lo que salió.
{ "id": "01M3DE9BPKN1YNWG20WCN0MN0V", "state": "PENDING" }
El motivo habitual para enviar de nuevo una línea entregada es que el receptor perdió la publicación, no que Reelwire fallara al enviarla. Un receptor que registra un id de entrega solo una vez procesado acepta la reentrega cuando perdió la publicación, y la ignora cuando no.
Una línea atascada en DELIVERING significa que el proceso murió tras reclamarla. Esas se liberan automáticamente; no necesita reentregarlas.
Un asistente de IA lee el mismo registro con reelwire_list_deliveries (una línea completa con su deliveryId) y envía una línea de nuevo con reelwire_redeliver.
Construir un receptor
No hace falta empezar de cero. La tarjeta Obtener su código de la pantalla Webhook es un receptor que funciona para copiar, en Node.js, Python, PHP o C#: comprueba la firma y la marca de tiempo como se indica abajo, omite un id de entrega que ya ha tratado y, para cada canal de un post.publish, informa de la publicación con POST /v1/posts/{postId}/report al Reelwire del que vino. Dos marcadores de posición ocupan el lugar de sus secretos, YOUR_SIGNING_SECRET (lo muestra Copiar el secreto) y YOUR_API_KEY, una clave con Posts: write; la parte que publica el video donde corresponde le toca escribirla a usted. Consulte Obtener su código.
Esté escrito en lo que esté escrito, un receptor hace estas cosas, en este orden:
- Leer el cuerpo en bruto. Verificar la firma sobre esos bytes antes de interpretarlo.
- Comprobar que la marca de tiempo está dentro de su ventana de desfase.
- Comprobar
x-reelwire-deliverycontra los ids que ya ha procesado, y responder 200 ante una repetición sin hacer el trabajo dos veces. Registrar un id una vez hecho su trabajo, no antes. - Responder 2xx y después hacer el trabajo: aceptar, poner en cola y responder, en lugar de publicar mientras Reelwire espera.
- Para un
post.publish, por cada entrada dechannels: asignarchannelIda su propia cuenta, descargarmedia[0].urly verificar susha256, publicarlo context, o con la longitud decaptionsque convenga a donde va, y después informar de vuelta con supostId:publishedcon el enlace, ofailedcon el motivo. - Para un
post.update, cambiar la publicación que hizo para cada canal, encontrada por supostId,platformPostIdopermalink, con el texto nuevo y, donde estámedia, el video nuevo. Para unpost.delete, retirarla e informardeleted. - Para un
ping, responder 2xx y nada más.
Treinta segundos es el límite para la respuesta, y una respuesta lenta se ve exactamente como una fallida.
Errores frecuentes
Verificar contra un cuerpo reserializado. JSON.stringify(req.body) no reproduce los bytes que se firmaron. Conserve el cuerpo en bruto.
Omitir la comprobación de la marca de tiempo, o la del id de entrega. Hacen falta ambas. La firma por sí sola no impide una repetición.
Comparar firmas con ===. Use una comparación de tiempo constante.
Leer solo el primer canal. Una entrega lleva todos los canales de su lote en channels.
Tomar su 2xx por el informe. Reelwire no marca nada como publicado por sí mismo. Hasta que informe de vuelta, la publicación se lee entregada en la pantalla Publicaciones.
Informar contra el id equivocado. Informe con channels[].postId, una llamada por canal. postKey nombra toda la publicación y handoverId una entrega de ella.
Esperar a un canal que no llega. Lea omitted: un canal con follows false no se entregará para esta publicación.
Guardar media[].url para más tarde. El enlace funciona siete días desde el envío, y un clip eliminado para hacer sitio responde 404 antes. Descargue el archivo mientras atiende la llamada.
Publicar en línea y responder tarde. Un receptor lento se ve como uno fallido tras 30 segundos, y entonces recibe la misma entrega de nuevo.
Responder 3xx. Una redirección es una entrega fallida, nunca una segunda solicitud.
Enrutar según la ruta de la URL. Todo evento llega a la única dirección. Enrute según x-reelwire-event.