Zum Hauptinhalt springen

Webhooks

Reelwire erstellt das Video. Ist ein Beitrag fällig, wird er übergeben, per E-Mail, per Webhook oder beidem, wie jeder seiner Kanäle es sagt, und wer ihn erhält, veröffentlicht ihn: Ihr eigenes Skript, eine Automatisierung, ein Veröffentlichungsdienst. (Ein Kanal, dessen Plattform-Connector eingeschaltet ist, wird zusätzlich von Reelwire selbst veröffentlicht: siehe Kanäle.) Diese Seite ist die Webhook-Hälfte: ein signierter Aufruf an eine von Ihnen gesetzte Adresse, der das Video, sein Titelbild und den Text für jeden Kanal der Übergabe trägt, und der Aufruf, den Sie zurück machen, sobald der Beitrag eines Kanals online ist. Änderungen und Entfernungen kommen an dieselbe Adresse. Eine Adresse bedient den ganzen Workspace: Sie betreiben einen Empfänger, nicht einen je Konto.

Das Kapitel Guides führt durch die drei üblichen Empfänger, von der ersten Anfrage bis zur Rückmeldung: Per Webhook übergeben.

MethodePfadBerechtigung
GET/v1/webhookwebhooks:read
PUT/v1/webhookwebhooks:write
POST/v1/webhook/secretangemeldete Person
POST/v1/webhook/secret/revealangemeldete Person
POST/v1/webhook/testwebhooks:write
POST/v1/posts/{postId}/reportposts:write
GET/v1/deliveriesconnections:read
GET/v1/deliveries/{id}connections:read
POST/v1/deliveries/{id}/redeliverconnections:write

Wohin Übergaben gehen, ist Konfiguration, und ein Schlüssel darf sie lesen und setzen. Das Rotieren des Signaturgeheimnisses gibt ein neues Geheimnis aus, und das erneute Anzeigen gibt das aktuelle aus, daher gehören beide, wie das Erstellen eines API-Schlüssels, einer angemeldeten Person mit der Berechtigung Verwaltung: Ein Schlüssel wird abgelehnt, was er auch enthält. Ein KI-Assistent, der sich durch Anmelden verbindet, hält auch webhooks:write nie, was auch immer die Person ihm erlaubt hat: Er darf lesen, wohin Übergaben gehen, aber die Adresse zu setzen oder eine Testzustellung zu senden erfordert einen auf der Seite API-Schlüssel von Hand erstellten Schlüssel oder eine Person. Siehe Die Verbindung eines Assistenten.

Einen Empfänger für den früheren Webhook gebaut?

Vier Dinge haben sich geändert, und ein für die frühere Form geschriebener Empfänger braucht alle vier:

  • Ein Aufruf trägt jeden Kanal einer Übergabe, in channels, jeder mit seinem eigenen postId, media und text. Der frühere Aufruf trug einen Kanal, mit channel, media, text und context auf oberster Ebene.
  • Ihre Antwort wird nicht mehr gelesen. Sie trug früher platformPostId und permalink und galt als Veröffentlichung des Beitrags. Jetzt ist der Beitrag eines Kanals veröffentlicht, wenn Sie es mit dem Rückmeldeaufruf sagen.
  • Jedes Ereignis geht an die eine von Ihnen gesetzte Adresse. Ein Update und ein Löschen gehen nicht mehr an die Adresse, deren /publish durch die Aktion ersetzt ist.
  • post.recall ist weg. Es war deklariert und wurde nie gesendet.

Wohin Übergaben gehen​

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 setzt die Adresse, mit { "url": "https://..." }, und antwortet mit der id und der url, wie gespeichert. Ein Schlüssel braucht webhooks:write; eine angemeldete Person braucht die Berechtigung Verwaltung, wie bei Zustellung testen und Erneut zustellen. Sie muss HTTPS sein, darf keinen Benutzernamen oder kein Passwort tragen, höchstens 2048 Zeichen ohne Leerzeichen oder Zeilenumbrüche haben und zu einer öffentlichen Adresse aufgelöst werden. Letzteres wird vor jeder Zustellung gegen das geprüft, wozu der Host aufgelöst wird, nicht nur beim Speichern.

POST /v1/webhook/secret, mit einem Sitzungstoken, rotiert das Signaturgeheimnis und gibt es einmal als secret zurück, genau wie ein API-Schlüssel einmal zurückgegeben wird. Niemand wird benachrichtigt: Der Empfänger ist ein Dritter und muss den Wert erhalten, und das ist der Schritt, den eine echte Integration ausführt und den ein gepushtes Geheimnis verbergen würde. Senden Sie { "secret": "..." }, um einen Wert zu setzen, den Sie schon halten, 24 bis 200 Zeichen, oder senden Sie {}, um einen erzeugen zu lassen.

POST /v1/webhook/secret/reveal, mit einem Sitzungstoken und { "password": "..." } (dem Passwort, mit dem sich die Person anmeldet), gibt das aktuelle Geheimnis erneut als secret zurück, wie das Anzeigen eines API-Schlüssels. Es braucht die Berechtigung Verwaltung, unterliegt dem Ratenlimit der Anmeldung, antwortet bei einem falschen Passwort mit 403 und wird in das Audit-Protokoll geschrieben. Kein API-Schlüssel kann es aufrufen. Auf der Seite Webhook sind diese beiden Secret kopieren und Secret erneuern, nebeneinander.

Testzustellung​

POST /v1/webhook/test sendet jetzt ein signiertes ping an die Adresse, während Sie warten, und antwortet mit dem, was zurückkam:

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
}

Es kommt genau wie eine Übergabe an, mit denselben drei Headern, auf dieselbe Weise signiert und unter derselben Regel zu öffentlichen Adressen, es ist also der Weg, Ihre Signatur und Ihre Wiederholungsbehandlung zu prüfen, bevor ein echter Beitrag Sie erreicht. Es wird einmal gesendet, nie wiederholt und wartet 15 Sekunden auf eine Antwort.

Wenn es nicht durchkommt, ist die Antwort trotzdem ein 200, mit ok false und error, das sagt warum: „The receiver answered 401: ...“, wo Ihr Empfänger geantwortet hat, oder was schiefging, wo keiner antwortete. Ohne gesetzte Adresse ist es ein 409. Jeder Test wird als eigene Zeile, zu keinem Beitrag, in das Zustellprotokoll geschrieben. Auf der Seite Webhook ist es der Button Zustellung testen neben der Adresse, und für einen KI-Assistenten ist es reelwire_test_webhook, das einen Schlüssel mit webhooks:write braucht (die Anmeldeverbindung eines Assistenten hat ihn nie, siehe oben).

Welche Kanäle hierher kommen​

Nur Kanäle mit eingeschaltetem Per Webhook übergeben (deliverByWebhook). Ein Kanal nur mit E-Mail erreicht Ihre Adresse nie: Seine Übergaben sind E-Mails an die Personen, die für den Workspace veröffentlichen (siehe Per E-Mail übergebene Beiträge). Ein Kanal mit beiden eingeschaltet wird auf beiden Wegen zugleich übergeben, und die E-Mail und Ihr Aufruf teilen eine handoverId. Ein Kanal, bei dem jeder Weg aus ist, erreicht niemanden des Workspaces selbst.

Ein Kanal mit zusätzlich eingeschaltetem Per Plattform-Connector übergeben wird auch von Reelwire selbst veröffentlicht, und nichts in Ihrem Aufruf sagt das: Ein Empfänger, der jeden Kanal veröffentlicht, den er erhält, würde diesen ein zweites Mal veröffentlichen.

Das Format eines Kanals entscheidet nichts darüber, wie er übergeben wird. Es entscheidet die Form des Clips und den dafür geschriebenen Text. Siehe Wie ein Beitrag übergeben wird.

Ein Aufruf je Übergabe​

Ein Beitrag ist ein Inhalt in einem Workspace: alles, was ein Feed-Element, ein Ereignis eines eigenen Feeds oder ein Beitrag einer Beitragsliste erzeugt, für jeden Kanal, der ihn empfängt. Jeder Kanal erhält weiterhin sein eigenes Rendering, in seinem eigenen Bildformat und seiner eigenen Sprache. postKey benennt den Beitrag und ist bei jeder Übergabe davon gleich. Eine erneute Veröffentlichung ist ein eigener Beitrag, mit einem eigenen postKey.

Ein Beitrag wird in Batches übergeben, nicht Kanal für Kanal:

  • Die Kanäle, die keine Freigabe brauchen, gehen zusammen, in einem Aufruf, sobald jeder von ihnen gerendert und fällig ist. Einer, der noch rendert, hält die anderen höchstens 30 Minuten zurück; dann gehen die anderen, und er folgt allein, in einem eigenen Aufruf.
  • Ein Kanal, der eine Freigabe braucht, geht für sich, sobald er freigegeben ist. Er hält die anderen nie zurück, und sie warten nie auf ihn.
  • Ein Kanal, der jetzt nicht kommt, wird weggelassen und genannt. Sein Render schlug fehl, er wurde abgebrochen oder abgelehnt, oder sein Kanal ist pausiert oder entfernt. omitted listet jeden Kanal des Beitrags auf, den ein Aufruf auslässt, mit dem Grund in Worten, und follows sagt, ob er noch von selbst kommt: Er wartet auf Freigabe, er rendert noch, er ist noch nicht fällig, oder sein Kanal ist pausiert, und er folgt, sobald der Kanal fortgesetzt wird.
  • Nichts wird übergeben, bevor es fällig ist.

Ein Beitrag für vier Kanäle, von denen einer eine Freigabe verlangt, kommt also als zwei Aufrufe: drei Kanäle zusammen und der vierte, sobald ihn jemand freigegeben hat. Beide tragen denselben postKey.

Eine Adresse für jedes Ereignis​

eventWann es kommt
post.publishEin Beitrag ist fällig: die Kanäle eines Batches, zum Veröffentlichen.
post.updateJemand hat einen übergebenen Beitrag geändert: seinen Text und vielleicht sein Video.
post.deleteJemand hat darum gebeten, einen übergebenen Beitrag zu entfernen.
pingEine Testzustellung. Sie gehört zu keinem Beitrag.

Jedes geht an die eine von Ihnen gesetzte Adresse. x-reelwire-event und das event des Textes unterscheiden sie, verzweigen Sie also danach, nie nach dem Pfad.

Ein Update und ein Löschen gehen auf den Wegen, die der Kanal des Beitrags jetzt eingeschaltet hat (E-Mail, Webhook, Plattform-Connector), oder auf denen davon, die die Anfrage in ways nennt, und nie auf einem, der aus ist: Einen zu nennen ist ein 409. Siehe Einen übergebenen Beitrag aktualisieren und Löschen.

Ein post.publish kann auch erneut für einen Beitrag kommen, den Sie schon hatten: Beitragsanfrage erneut senden auf der Seite Beiträge mit gewähltem Webhook, POST /v1/jobs/{id}/resend-webhook, sendet ihn aufgebaut, wie der Beitrag jetzt lautet, mit einer neuen deliveryId und handoverId, demselben postKey und denselben postIds und einem leeren omitted, sodass ein Empfänger, der die verarbeiteten Zustell-IDs behält, erneut danach handelt. Ein Beitrag, der fehlschlug und erneut versucht wird, POST /v1/jobs/{id}/retry (Beitragsanfrage erneut senden bei einem fehlgeschlagenen Beitrag), kommt genauso: ein frisches post.publish mit neuen IDs, wie der Beitrag jetzt lautet. Siehe Einen fehlgeschlagenen Beitrag erneut versuchen.

Header bei jeder Zustellung​

HeaderWert
content-typeapplication/json; charset=utf-8
x-reelwire-signaturet=<unix seconds>,v1=<hex hmac-sha256>
x-reelwire-deliveryDie Zustell-ID, eine ULID. Bei jedem Wiederholungsversuch einer Zustellung dieselbe.
x-reelwire-eventpost.publish, post.update, post.delete oder ping

Der Zeichensatz wird ausdrücklich deklariert, weil ein Empfänger, der ihn ableitet, UTF-8 als latin1 dekodieren kann, was einen Geviertstrich in einer Beschreibung in Mojibake verwandelt und, schlimmer, die Bytes ändert, gegen die die Signatur geprüft wird.

Die Signatur verifizieren​

Die signierte Zeichenfolge ist `${t}.${rawBody}`, HMAC-SHA256 mit Ihrem Geheimnis, hexadezimal kodiert. Das ist die Konstruktion von Stripe, und aus demselben Grund: Den Zeitstempel in die signierte Zeichenfolge zu binden macht eine Abweichungsprüfung erst sinnvoll. Ohne ihn bleibt ein abgefangener Text ewig gültig.

Ihr Signaturgeheimnis ist alles, was Sie brauchen: Jeder Workspace hat ein eigenes, eine Zustellung, die mit Ihrem verifiziert, stammt also aus Ihrem Workspace und von niemand anderem. Lesen Sie t und v1 aus dem Header.

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);
}

Zwei Prüfungen, beide erforderlich. Lehnen Sie einen Zeitstempel ab, der mehr als 300 Sekunden von Ihrer eigenen Uhr abweicht, und lehnen Sie ein x-reelwire-delivery ab, das Sie schon verarbeitet haben. Die Signatur allein verhindert keine Wiederholung.

Der Text wird vor dem Signieren kanonisiert: Objektschlüssel sortiert, Undefiniertes weggelassen, sodass die Bytes, die Sie erhalten, genau die Bytes sind, die signiert wurden. Jeder Versuch wird frisch signiert: Ein Wiederholungsversuch trägt dieselbe Zustell-ID, mit neuem Zeitstempel, dem nächsten attempt und neu signierten Medienlinks.

Was eine Veröffentlichung trägt​

Ein post.publish für einen Beitrag, dessen Instagram-Reels-Kanal keine Freigabe braucht, während sein TikTok-Kanal auf einen Prüfer wartet:

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

Ein signierter Aufruf pro Übergabe, dessen Header und Body den Beitrag jedes Kanals mit Video, Standbild und Texten tragen

Die Schlüssel kommen alphabetisch sortiert an, weil der Text kanonisiert wird; hier sind sie in Lesereihenfolge gezeigt.

Bemerkenswerte Punkte:

  • channels enthält jeden Kanal des Batches. Gehen Sie darüber in einer Schleife: Ein Beitrag für drei Kanäle ohne Prüfer ist ein Aufruf mit drei Einträgen. postId ist das, wogegen Sie zurückmelden; postGroupId ist der Beitrag des Kanals auf der Seite Beiträge und channelId der Kanal.
  • Es gibt keinen Konto-Handle. channelId nennt das Ziel; der Empfänger ordnet es seinem eigenen Konto zu, die einzige Zuordnung, die stimmen kann. Reelwire hat nie Ihre Kontonamen gehalten, und ein Feld, das es nur mit einer Vermutung füllen könnte, ist schlimmer als kein Feld. channelName ist der Name, den der Workspace dem Kanal gegeben hat, eine Beschriftung für einen Menschen, der ein Protokoll liest.
  • platform und surface sind das Format des Kanals. Sie sagen, für welche Form und welche Textlimits der Clip gemacht wurde. Sie versprechen nichts darüber, wer ihn veröffentlicht oder wie. platform ist eines von youtube, facebook, instagram, tiktok, linkedin, telegram, discord, slack, threema, x, wechat, signal, pinterest, threads, whatsapp oder download; die neun nach telegram kamen mit den Plattform-Connectors hinzu, ein Empfänger, der den Wert gegen eine Liste prüft, sollte sie also auch nehmen.
  • media[0] ist das Video und media[1] sein Titelbild, ein JPEG in derselben Form, wo der Renderer eines erstellt hat.
  • sha256 wird nach dem Download geprüft und nicht als Hinweis behandelt. Eine Abweichung ist ein harter Fehlschlag.
  • media[].url wird bei jedem Senden neu signiert und funktioniert ab dann sieben Tage, sodass ein Empfänger, der einen Beitrag für eine eigene Veröffentlichungszeit einreiht, die Datei noch findet. Ein Clip, der inzwischen aus dem Speicher des Workspaces entfernt wurde, um Platz für neuere zu schaffen, antwortet mit 404, was der Link auch sagt, laden Sie ihn also bald herunter.
  • text ist bereits lokalisiert und an das Format des Kanals angepasst. Der Empfänger übersetzt nicht. disclaimer ist der eigene Text der Marke, getrennt von der Beschreibung gehalten, damit Sie ihn nach den eigenen Worten des Beitrags platzieren können. title kommt, wo die Plattform ein Titelfeld hat, und linkUrl und linkLabel tragen den Link der Marke, wo das Format einen liefern kann.
  • captions kommt nur bei einem Kanal auf Keine bestimmte Plattform (platform download), der nicht nennt, wohin seine Clips gehen. Es enthält dann die Beschreibung in jeder Länge, in der der Beitrag geschrieben wurde, bis zu vier, eine je Art von Plattform, jede mit ihrer length, einem label, das sie und die Plattformen nennt, für die sie geschrieben ist, und ihrem eigenen text: tiny für Pinterest, Threads und X, short für Telegram, Threema, WeChat und WhatsApp, medium für Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack und TikTok, long für Facebook Feed und YouTube. Es entfällt, wo der Beitrag eine Beschreibung hat oder wo jede Länge gleich lesen würde. tiny ist neu seit Release 0.76.0, ein Empfänger, der length gegen eine Liste prüft, sollte es also auch nehmen. Ein Kanal, der seine Plattform nennt, wie der oben, erhält den Text dieser Plattform in text und keine captions.
  • posting sind die Veröffentlichungseinstellungen des Kanals, mit ausgefüllten Standards. Jeder Schalter ist da; die, die sein Format nicht hat, stehen immer auf ihren Standards und bedeuten dafür nichts, und visibility kommt nur, wo das Format eine Wahl bietet und eine getroffen wurde (fehlend ist öffentlich). Welches Format welche hat, steht unter Veröffentlichungseinstellungen. Sie anzuwenden ist Ihre Sache, wenn Sie veröffentlichen; der Plattform-Connector eines Kanals wendet sie selbst an, und das Beitragspaket listet sie für den auf, der von Hand veröffentlicht. Auch ein post.update trägt sie, wie sie beim Senden der Änderung sind. notifySubscribers (YouTube, an, sofern nicht ausgeschaltet), shareToFeed (Instagram Reels, an, sofern nicht ausgeschaltet) und protectContent (Telegram: Niemand kann den Beitrag weiterleiten oder das Video speichern) sind neu seit Release 0.77.0; bei X, Threads und Pinterest ist allowComments jetzt die eigene Einstellung des Kanals, wo es vorher immer an war.
  • template ist der exakte Build, der gerendert wurde, nie ein Alias, mit variant, dem Zuschnitt, in dem der Clip gerendert wurde (der des Kanals oder der eigene eines Beitrags einer Beitragsliste), wo es einen gibt.
  • approval ist none oder per_post_approved, mit approvedBy und approvedAt, wo ein Prüfer es freigegeben hat.
  • source sagt, woher der Inhalt kam: broadcast, ein Feed, den Reelwire mitbringt, mit sourceSlug und formatSlug; stream, einer Ihrer eigenen Feeds, mit streamId; oder queue, eine Beitragsliste, mit queueName und dem eigenen title des Beitrags.
  • handoverId wird von der E-Mail und dem Aufruf einer Übergabe geteilt. attempt beginnt bei 1 und zählt die Sendungen dieser Zustellung.
  • syndication steht nie in einem Aufruf an Ihren eigenen Webhook. Es steht in einem Aufruf an den Webhook eines Ihrer Syndikations-Teilnehmer, { "partnerId", "from" }, der dasselbe Dokument zugeschnitten auf die von ihnen gewählten Kanäle trägt, mit dem Geheimnis ihrer Gruppe signiert, und in einem Test-ping, den die Seite ihrer Gruppe an einen Webhook sendet, den sie schon haben.

Ein Update, ein Löschen und ein 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 ist der ganze Text, wie er jetzt lauten soll, kein Unterschied, und captions kommt, wo der Kanal auf Keine bestimmte Plattform ist. media ist nur da, wenn das Video ersetzt wurde: Es enthält dann das neue Video, eine Datei aus der Mediathek des Workspaces, mit assetId, kind video, mimeType, byteSize, sha256, url und expiresAt, signiert wie der Clip einer Veröffentlichung. Ohne es behalten Sie das Video, das Sie haben. platformPostId und permalink sind das, was für diesen Kanal zurückgemeldet wurde, wo es jemand tat, damit Sie den Beitrag wiederfinden. posting sind die Veröffentlichungseinstellungen des Kanals, wie sie jetzt sind, wie eine Veröffentlichung sie trägt.

Reelwire ändert nichts dort, wo der Beitrag ist: Das tut Ihr Empfänger. Der Beitrag in Reelwire übernimmt den neuen Text, sobald der Aufruf durchgekommen ist.

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

Den Beitrag endgültig entfernen, nicht verbergen, dann deleted melden. Bis jemand es tut, liest der Kanal auf der Seite Beiträge Löschen angefordert.

ping​

{
"event": "ping",
"deliveryId": "01M3DF0R6K9W2E5T8Y1B4N7M3Q",
"occurredAt": "2026-09-25T23:39:41.702Z",
"attempt": 1,
"message": "reelwire webhook test"
}

Was Sie antworten​

Jedes 2xx ist eine Zustellung. Der Text wird für das Zustellprotokoll aufbewahrt und nicht gelesen, das hier genügt also:

{ "received": true }

Ein 2xx sagt, dass Sie die Übergabe haben, nichts weiter. Es veröffentlicht nichts und markiert nichts als veröffentlicht: Der Beitrag des Kanals bleibt auf der Seite Beiträge übergeben, bis Sie zurückmelden.

Alles andere, auch eine Weiterleitung, ist eine fehlgeschlagene Zustellung und wird erneut versucht. Einer Weiterleitung wird nie dorthin gefolgt, wohin sie zeigt. Es wird nur an öffentliche Adressen zugestellt. Ein 410 Gone ist hier ein Fehlschlag wie jeder andere und schaltet nichts ab: Diese Antwort nimmt nur den Webhook eines Syndikationsteilnehmers aus seiner Gruppe.

Zurückmelden​

Ob der Beitrag eines Kanals hinausging, sagen Sie gesondert, je Kanal, sobald Sie es wissen:

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/"
}

Es braucht einen API-Schlüssel mit Posts write, posts:write. Die ID ist channels[].postId aus der Übergabe; auch die id eines Kanals aus GET /v1/jobs funktioniert.

FeldWas es ist
statusErforderlich: published, skipped, failed oder deleted.
urlMit published: wo der Beitrag ist, eine http- oder https-Adresse von bis zu 2000 Zeichen.
platformPostIdMit published: die eigene ID der Plattform für den Beitrag, bis zu 200 Zeichen.
reasonMit failed: warum er nicht veröffentlicht werden konnte, bis zu 1000 Zeichen, in Worten, die eine Person auf der Seite Beiträge liest.
statusAkzeptiert, solange der BeitragEr wird
publishedPUBLISHING, HANDED_OVER, SKIPPED oder FAILEDPUBLISHED, mit auf jetzt gesetztem publishedAt und dem Link auf der Seite Beiträge
skippedHANDED_OVERSKIPPED: übergeben und bewusst nicht veröffentlicht
failedPUBLISHING oder HANDED_OVERFAILED, mit dem Grund auf der Seite Beiträge
deletedHANDED_OVER oder PUBLISHEDRECALLED, was die Seite Beiträge als Gelöscht zeigt

published wird akzeptiert, solange der Beitrag noch PUBLISHING ist, weil ein Empfänger ihn veröffentlichen und zurückmelden kann, bevor er den Aufruf beantwortet hat, der ihn brachte; Ihre Antwort macht die Meldung nicht rückgängig. Ein übersprungener oder fehlgeschlagener Beitrag kann später immer noch als veröffentlicht gemeldet werden.

Dasselbe erneut zu sagen ist in Ordnung: Nichts bewegt sich, und der neueste Link und die neueste ID bleiben erhalten. Alles, was der Zustand des Beitrags nicht erlaubt, ist ein 409, der den Zustand nennt: „This post is published, so it cannot be reported skipped.“ Ein unbekannter status oder ein Link, der keine Webadresse ist, ist ein 400, und eine ID, die keinem Beitrag des Workspaces gehört, ein 404.

Der Link und die platformPostId, die Sie melden, reisen mit jedem späteren post.update und post.delete für diesen Kanal, damit wer den Beitrag hat ihn wiederfinden kann.

Dieselbe Meldung ist Als veröffentlicht markieren, Überspringen und Als gelöscht markieren auf der Seite Beiträge und im Beitragspaket aus der E-Mail und reelwire_report_post für einen KI-Assistenten. Wer es auch sagt, die Regeln sind diese.

Wiederholungsversuche​

Sieben Versuche über etwas mehr als drei Stunden, dann ist die Zustellung tot:

VersuchWartezeit davor
1sofort
210 Sekunden
31 Minute
45 Minuten
515 Minuten
61 Stunde
72 Stunden

Jeder Aufruf hat 30 Sekunden zum Antworten, bevor er als fehlgeschlagen zählt. Wenn die Leiter zu Ende ist:

  • Eine Veröffentlichung, die auf gar keinem Weg durchkam, setzt die Beiträge ihrer Kanäle auf FAILED, mit dem Grund auf der Seite Beiträge. Wo auch ein anderer Weg eingeschaltet ist, die E-Mail oder der Plattform-Connector, und er ging oder ist noch unterwegs, bleiben sie dort, wo dieser sie hinsetzt, und die Seite Beiträge sagt, was beim Webhook schiefging.
  • Ein Update oder ein Löschen lässt den Beitrag genau, wie er war, und sagt warum, statt vorzugeben, er habe sich geändert.

Eine E-Mail-Zeile steigt dieselbe Leiter hinauf. Die Zeile eines Plattform-Connectors tut es auch, mit acht Minuten für jeden Versuch, außer dass eine Ablehnung, die jedes Mal wiederkäme, etwa ein falsches Token oder ein für die Plattform zu großes Video, sie sofort stoppt.

GET /v1/overview trägt attention.deadDeliveries, die Zahl, bei der Sie alarmieren sollten.

Beobachten und erneut senden​

GET /v1/deliveries ist das Zustellprotokoll, die neuesten zuerst: eine Zeile je Weg nach draußen jeder Übergabe, sodass eine Übergabe, die per E-Mail und per Webhook ging, zwei Zeilen sind. Filtern Sie mit state, mechanism (EMAIL, WEBHOOK oder CONNECTOR) und event, suchen Sie mit q (dem Namen eines Kanals, dem Fehler, der Adresse, an die eine Zeile ging), grenzen Sie mit since und until auf einen Zeitraum ein und ordnen Sie mit sort (createdAt, der Standard, oder updatedAt) und ascending. Es wird wie jedes andere Protokoll paginiert: page ab 1 und perPage bis 200, 40, wenn weggelassen (limit wird gleich gelesen), und die Antwort sagt total, wie viele passen, und all, wie viele der Zeitraum enthält.

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 sind die Werte, die der Zeitraum tatsächlich enthält, sodass ein Filter nie eine Auswahl anbietet, die die Liste leert.

state läuft PENDING, DELIVERING, RETRYING, DELIVERED, DEAD. postIds sind die Beiträge der Kanäle, die die Zeile trug, und channels die Namen ihrer Kanäle; postId und platform sind nur bei einer Zeile gefüllt, die einen trug. mechanism ist EMAIL, WEBHOOK oder CONNECTOR für einen Beitrag, den der Plattform-Connector eines Kanals erstellt hat: eine Zeile je Beitrag, deren response die ID der Plattform und den Link zum Beitrag enthält. note ist, was ein Connector zu einer Zeile sagte, die trotzdem hinausging, etwa ein LinkedIn-Beitrag, dessen Kommentare er nicht schließen konnte; überall sonst ist es null.

GET /v1/deliveries/{id} antwortet mit einer Zeile ganz:

{
"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 }
}

Es trägt außerdem payload, oben weggelassen: den Text, wie er zuletzt hinausging, mit den Links, mit denen er damals signiert wurde. to ist die Adresse, an die der Aufruf ging, auf der Zeile aufbewahrt, weil der Workspace seinen Webhook später woandershin richten kann; bei einer E-Mail sagt es, an wen die E-Mail geht, und bei einer Connector-Zeile, als wen der Plattform-Connector veröffentlicht hat. resend ist true bei einer Übergabe, um die jemand erneut gebeten hat, etwa Beitragsanfrage erneut senden auf der Seite Beiträge. response ist das, was zurückkam: das JSON des Empfängers oder raw mit dem Anfang seiner Worte.

POST /v1/deliveries/{id}/redeliver sendet eine Zeile erneut, zugestellt oder nicht: einen Webhook-Aufruf mit derselben Zustell-ID und neu signierten Links, eine E-Mail erneut, wie sie war. Die Versuchszählung beginnt von vorn. Eine Veröffentlichung, bei der jeder Weg gestorben war, setzte die Beiträge ihrer Kanäle auf FAILED; sie erneut zu senden bringt sie zurück zu übergeben, sodass der Erfolg der Zeile bei ihnen ankommt. Ein Schlüssel braucht connections:write; eine angemeldete Person braucht die Berechtigung Verwaltung. Eine Connector-Zeile, die durchkam, ist ein 409: Eine Plattform kennt keine Zustell-ID und würde den Beitrag ein zweites Mal annehmen. Auf der Seite Aktivität ist es Erneut zustellen bei einer Zeile von Versendeter Inhalt.

{ "id": "01M3DE9BPKN1YNWG20WCN0MN0V", "state": "PENDING" }

Der übliche Grund, eine zugestellte Zeile erneut zu senden, ist, dass der Empfänger den Beitrag verloren hat und nicht, dass Reelwire ihn nicht senden konnte. Ein Empfänger, der eine Zustell-ID erst aufzeichnet, wenn er sie verarbeitet hat, nimmt die erneute Zustellung an, wenn er den Beitrag verloren hat, und ignoriert sie, wenn nicht.

Eine Zeile, die in DELIVERING hängt, bedeutet, dass der Prozess nach dem Beanspruchen gestorben ist. Diese werden automatisch freigegeben; Sie müssen sie nicht erneut zustellen.

Ein KI-Assistent liest dasselbe Protokoll mit reelwire_list_deliveries (eine Zeile ganz mit ihrer deliveryId) und sendet eine Zeile mit reelwire_redeliver erneut.

Einen Empfänger bauen​

Sie müssen nicht bei null anfangen. Die Karte Code abrufen auf der Seite Webhook ist ein funktionierender Empfänger zum Kopieren, in Node.js, Python, PHP oder C#: Er prüft die Signatur und den Zeitstempel wie unten, überspringt eine Zustell-ID, die er schon behandelt hat, und meldet für jeden Kanal eines post.publish den Beitrag mit POST /v1/posts/{postId}/report an das Reelwire zurück, von dem er kam. Zwei Platzhalter stehen für Ihre Geheimnisse, YOUR_SIGNING_SECRET (Secret kopieren zeigt es) und YOUR_API_KEY, ein Schlüssel mit Posts: write; der Teil, der das Video dorthin stellt, wo es hingehört, ist Ihre Sache. Siehe Code abrufen.

In welcher Sprache er auch geschrieben ist, ein Empfänger tut diese Dinge, in dieser Reihenfolge:

  1. Den rohen Text lesen. Die Signatur über diese Bytes verifizieren, bevor geparst wird.
  2. Prüfen, dass der Zeitstempel innerhalb Ihres Abweichungsfensters liegt.
  3. x-reelwire-delivery gegen die IDs prüfen, die Sie schon verarbeitet haben, und bei einer Wiederholung mit 200 antworten, ohne die Arbeit zweimal zu tun. Eine ID erst aufzeichnen, wenn ihre Arbeit getan ist, nicht vorher.
  4. Mit 2xx antworten, dann die Arbeit tun: annehmen, einreihen und antworten, statt zu veröffentlichen, während Reelwire wartet.
  5. Bei einem post.publish, für jeden Eintrag in channels: channelId Ihrem eigenen Konto zuordnen, media[0].url herunterladen und seinen sha256 verifizieren, es mit text veröffentlichen oder mit der Länge aus captions, die zu dem passt, wohin es geht, und dann mit seiner postId zurückmelden: published mit dem Link oder failed mit dem Grund.
  6. Bei einem post.update den Beitrag ändern, den Sie für jeden Kanal erstellt haben, gefunden über seine postId, platformPostId oder permalink, mit dem neuen Text und, wo media da ist, dem neuen Video. Bei einem post.delete ihn herunternehmen und deleted melden.
  7. Bei einem ping mit 2xx antworten und sonst nichts.

Dreißig Sekunden sind das Limit für die Antwort, und eine langsame Antwort sieht genau wie eine fehlgeschlagene aus.

Häufige Fehler​

Gegen einen neu serialisierten Text verifizieren. JSON.stringify(req.body) reproduziert nicht die Bytes, die signiert wurden. Behalten Sie den rohen Text.

Die Zeitstempelprüfung oder die Zustell-ID-Prüfung überspringen. Beide sind nötig. Die Signatur allein verhindert keine Wiederholung.

Signaturen mit === vergleichen. Verwenden Sie einen Vergleich in konstanter Zeit.

Nur den ersten Kanal lesen. Eine Übergabe trägt in channels jeden Kanal ihres Batches.

Ihr 2xx für die Meldung halten. Reelwire markiert von sich aus nichts als veröffentlicht. Bis Sie zurückmelden, liest der Beitrag auf der Seite Beiträge übergeben.

Gegen die falsche ID melden. Melden Sie mit channels[].postId, ein Aufruf je Kanal. postKey benennt den ganzen Beitrag und handoverId eine Übergabe davon.

Auf einen Kanal warten, der nicht kommt. Lesen Sie omitted: Ein Kanal mit follows false wird für diesen Beitrag nicht übergeben.

media[].url für später speichern. Der Link funktioniert sieben Tage ab dem Senden, und ein Clip, der entfernt wurde, um Platz zu schaffen, antwortet vorher mit 404. Laden Sie die Datei herunter, während Sie den Aufruf bearbeiten.

Inline veröffentlichen und spät antworten. Ein langsamer Empfänger sieht nach 30 Sekunden wie ein fehlgeschlagener aus, und dann erhalten Sie dieselbe Übergabe erneut.

Mit 3xx antworten. Eine Weiterleitung ist eine fehlgeschlagene Zustellung, nie eine zweite Anfrage.

Nach dem URL-Pfad verzweigen. Jedes Ereignis kommt an die eine Adresse. Verzweigen Sie nach x-reelwire-event.