Sign inGet Started

WhatsApp-Nachrichten senden

Dieser Leitfaden behandelt den Sende-Endpoint POST /v1/whatsapp/messages. Sie erstellen ein JSON-Payload mit einem Empfänger und genau einem Inhaltstyp: ein vorab genehmigtes Template oder eine Service-Nachricht mit Text, einem Bild, Video, Audio, einem Sticker, einem Dokument, einem Standort, Kontaktkarten oder einem antippbaren Element. Bird gibt 202 Accepted mit einer Nachrichten-ID zurück und stellt asynchron zu. Welchen der beiden Typen Sie senden können, hängt vom Kundenservice-Fenster ab. Jeder Request sendet eine Nachricht an einen Empfänger, und es gibt keinen Batch-Endpoint.

Ein minimaler Send

Das kleinste gültige Payload besteht aus einem to-Empfänger und einem template mit seinem slug. Fügen Sie language hinzu, wenn Sie eine bestimmte Sprache wünschen; ohne diese Angabe wird die Standardsprache des Templates gesendet. Füllen Sie alle Variablen, die das Template deklariert, über components.
Der curl-Aufruf nennt den US-Host; wenn Ihr Schlüssel mit bk_eu1_ beginnt, rufen Sie stattdessen https://eu1.platform.bird.com auf. Die SDKs lesen die Region aus Ihrem Schlüssel und setzen daher keinen Host.
const msg = await bird.whatsapp.send({
  to: "+15551234567",
  template: {
    slug: "bird_otp",
    components: [{ type: "body", parameters: [{ type: "text", text: "123456" }] }],
  },
});
console.log(msg.id, msg.status);

Das Kundenservice-Fenster

Welchen der beiden Typen Sie senden können, hängt von einem einzigen Zustand ab: ob das Kundenservice-Fenster geöffnet ist.
Der Kontakt öffnet das Fenster, indem er Ihre Geschäftsnummer anschreibt oder anruft, und es bleibt 24 Stunden offen, wobei es sich bei jeder weiteren Nachricht zurücksetzt. Solange es offen ist, können Sie eine Service-Nachricht senden, also beliebigen freien Inhalt: Text, Bild, Video, Audio, Sticker, Dokument, Standort oder Interaktiv. Sobald es abläuft, erreicht nur noch ein vorab genehmigtes Template den Kontakt, und dessen Antwort darauf öffnet das Fenster erneut.
Bird verfolgt das Fenster für Sie, sodass eine Service-Nachricht in ein geschlossenes Fenster abgelehnt wird, bevor etwas erstellt oder berechnet wird: Der Request gibt einen 422 E15044 WhatsAppServiceWindowClosed zurück. Die Prüfung erfolgt nach dem Best-Effort-Prinzip und scheitert offen, sodass ein 202 kein Beweis dafür ist, dass das Fenster beim Dispatch tatsächlich offen war; ein Fenster, das zwischen Annahme und Dispatch abläuft, schlägt asynchron fehl, mit service_window_expired auf dem last_error der Nachricht.
Unter Kundenservice-Fenster finden Sie den vollständigen Lebenszyklus: was es öffnet, was es zurücksetzt und wie es mit der Preisgestaltung zusammenwirkt.

Das Payload aufbauen

Empfänger

to benennt ein Ziel, angegeben als Telefonnummer, geschäftsbezogene Benutzer-ID oder Gruppen-ID. Eine Telefonnummer steht im E.164-Format: ein führendes +, Ländervorwahl und Teilnehmernummer, zum Beispiel +14155550100. Wir validieren die Nummer, sodass ein Wert, der keine reale, wählbare Nummer sein kann (falsche Länge, nicht zugewiesenes Präfix), mit einem 422 WhatsAppInvalidRecipient abgelehnt wird, bevor etwas berechnet wird. Es gibt kein Empfänger-Array und keinen Batch-Versand, sodass das Erreichen vieler Personen, die nicht in einer Gruppe sind, einen Aufruf pro Empfänger erfordert.
Eine geschäftsbezogene Benutzer-ID wie US.13491208655302741918 adressiert einen Kontakt, dessen Telefonnummer Sie nicht haben, und ist die Methode, um einem Kontakt zu antworten, der Sie ohne Nummer erreicht hat. Zwei Dinge ändern sich: Die Sendenummer muss zum selben Business-Portfolio gehören, auf das die ID bezogen ist, und ein Einmalpasscode-Template benötigt eine Telefonnummer. Ein von Bird verwaltetes wird bei der Annahme mit einem 422 WhatsAppRecipientNotSupportedForTemplate abgelehnt; ein Authentifizierungstemplate, das Ihr Workspace erstellt hat, wird angenommen und schlägt dann fehl, da Meta eine Telefonnummer dafür verlangt.
to nimmt noch eine weitere Form an: eine WhatsApp-Gruppen-ID wie wag_01krdgeqcxet5s7t44vh8rt9mg, die an jeden Teilnehmer dieses Gruppenchats sendet. Ein Gruppenversand lässt from weg und meldet die Zustellung gruppenweit statt für einen einzelnen Empfänger, daher behandelt Senden an eine WhatsApp-Gruppe dies auf einer eigenen Seite.

Template

template benennt das vorab genehmigte Template, das gesendet werden soll:
  • slug (erforderlich): Der Slug des Templates, zum Beispiel bird_order_confirmation. Er muss einem Template in Ihrem Katalog entsprechen (Kleinbuchstaben, Ziffern und Unterstriche).
  • language: Das Sprach-Tag des Templates, zum Beispiel en oder pt-BR. Lassen Sie es weg, um die Standardsprache des Templates zu senden; die Angabe einer Sprache, die das Template nicht hat, gibt einen 422 zurück, der die verfügbaren auflistet. Die angenommene Nachricht gibt die aufgelöste Sprache zurück.
  • components: Die Werte, die die Variablen des Templates füllen (siehe Komponenten und Parameter). Lassen Sie es weg, wenn das Template keine Variablen hat.
Durchsuchen Sie Ihre Templates, deren Sprachen und eine gerenderte Vorschau auf der Seite Templates.

Komponenten und Parameter

Templates enthalten Variablen, benannt ({{ref}}, {{amount}}) oder nummeriert ({{1}}, {{2}}). Sie liefern deren Werte über components. Jede Komponente benennt einen type (body oder button) und ein parameters-Array. Jeder Parameter benennt seinen eigenen type (text, image, video, gif, document oder location) und enthält das passende Feld: text einen einfachen String, image/video/gif/document eine öffentliche https url, und location einen Punkt auf der Karte. Ein Template mit benannten Parametern erfordert einen name auf jedem Parameter, der exakt den vom Template deklarierten Namen entsprechen muss (siehe Feldreferenz). Ein positionelles Template lässt name weg und nimmt seine Werte stattdessen in {{n}}-Reihenfolge, sodass der erste Parameter {{1}} füllt. In beiden Fällen geben Parameter, die nicht dem Template entsprechen, einen 422 WhatsAppTemplateParameterMismatch zurück. Ein header-Komponententyp existiert ebenfalls auf der Leitung: Bei einem von Bird verwalteten Template wird er verworfen, da kein von Bird verwaltetes Template eine Header-Variable deklariert, aber bei einem von Ihrem Workspace erstellten Template wird er weitergeleitet, wodurch ein Media-Header-Utility- oder Marketing-Template sein Bild erhält.
Zum Beispiel: Ein Einmalpasscode-Template, dessen Body {{1}} is your verification code lautet und dessen Button den Code kopiert, nimmt den Code sowohl als Body-Parameter als auch als Button-Parameter, positionell (kein name):
Codebeispiel
{
  "components": [
    { "type": "body", "parameters": [{ "type": "text", "text": "481920" }] },
    { "type": "button", "parameters": [{ "type": "text", "text": "481920" }] }
  ]
}

Kategorie und Absender

Die Kategorie eines Templates (authentication, utility oder marketing) bestimmt, wie WhatsApp die Nachricht behandelt, und zusammen mit dem Zielland, was sie kostet.
Wer den Absender besitzt, entscheidet, ob Sie ihn angeben:
  • Ein von Bird verwaltetes Template (sein Slug beginnt mit bird_) sendet von der Nummer, die Bird für diese Kategorie vorhält, daher lassen Sie from weg. Es zu setzen gibt einen 422 WhatsAppSenderNotAllowed zurück.
  • Alles andere benennt seinen eigenen Absender in from: eine Service-Nachricht jedes Typs und jedes Template, das Ihr Workspace erstellt hat. Die Nummer muss eine sein, die Ihr Workspace besitzt. Sie wegzulassen gibt einen 422 WhatsAppSenderRequired zurück, und eine Nummer, von der der Workspace nicht senden kann, gibt einen 422 WhatsAppSenderNotFound zurück. Ein selbst erstelltes Template muss außerdem auf demselben WhatsApp Business Account liegen wie die Nummer, sonst gibt der Send einen 422 WhatsAppSenderWABAMismatch zurück.
Telefonnummer-Einrichtung behandelt beide Typen von Nummern und wie eine eigene Nummer verbunden wird.

Service-Nachrichten

Statt template übergeben Sie genau eines von text, image, video, audio, sticker, document, location, contact_cards oder interactive. Alle neun sind Service-Nachrichten und benötigen daher ein offenes Kundenservice-Fenster. Jede davon erfordert außerdem from, eine Nummer, die Ihr Workspace besitzt; die verwalteten Nummern von Bird können sie nicht übertragen.
  • text: { "body": "..." }, bis zu 4096 Zeichen. Fügen Sie "preview_url": true hinzu, um eine Link-Vorschau für die erste URL in body zu rendern.
  • image, video, audio, sticker, document: Jedes nimmt eine öffentliche https-URL, die WhatsApp zur Sendezeit abruft (url), sodass eine signierte URL den Send überdauern muss. Eine http-URL wird direkt abgelehnt. WhatsApp ruft die Datei selbst ab, sodass eine URL, die nicht erreichbar ist, die einen nicht unterstützten Typ liefert oder eine Datei über dem Größenlimit für ihren Typ darstellt, angenommen wird und dann fehlschlägt, mit media_rejected auf dem last_error der Nachricht und dem eigenen Grund von WhatsApp in description. image, video und document nehmen außerdem ein optionales caption; document nimmt außerdem ein optionales filename; audio nimmt ein optionales voice-Flag für ein Sprachnachrichten-Rendering.
  • location: { "latitude": ..., "longitude": ... } (beide erforderlich, Dezimalgrad) plus optionales name und address.
  • contact_cards: Ein Array von bis zu fünf Kontakten, die in einer Nachricht geteilt werden. Der name jeder Karte benötigt formatted_name plus mindestens einen weiteren Teil (first_name, last_name, middle_name, prefix oder suffix); phone_numbers, emails, urls und addresses nehmen jeweils bis zu zehn Einträge, und org und birthday (als YYYY-MM-DD) sind optional. Eine phone_number in E.164 verleiht dieser Karte einen Button, der einen Chat damit öffnet.
  • interactive: Body-Text plus ein antippbares Element, in einem von sechs Typen: Antwort-Buttons, ein Listenmenü, ein Link-Button, ein Medien-Karussell oder ein einzelner Button, der den Empfänger nach seinem Standort oder seiner Telefonnummer fragt. Interaktive Nachrichten behandelt die Wire-Struktur jedes Typs, die Antworten, die ein Tippen erzeugt, und die Limits.
Codebeispiel
{
  "to": "+16505551234",
  "from": "+13124495648",
  "text": { "body": "Your order shipped: https://example.com/track/A1B2C3", "preview_url": true }
}
Ein Request ohne Inhalt oder mit mehr als einem Typ wird mit einem 422 abgelehnt.

Eine Nachricht zitieren

Setzen Sie in_reply_to_message_id auf eine WhatsApp-Nachrichten-ID, um Ihre Nachricht als Antwort darauf zu senden, so wie das Tippen auf Antworten in der WhatsApp-App eine Nachricht zitiert. Der Empfänger sieht Ihre Nachricht mit der zitierten darüber, und das Feld wird bei jedem Lesen der Nachricht zurückgegeben.
Es funktioniert auch umgekehrt: Eine eingehende Nachricht, die WhatsApp als Antwort markiert, enthält die ID der zitierten Nachricht im selben Feld, und so erkennen Sie, auf welche Ihrer Nachrichten eine Antwort sich bezieht. Eine eingehende Nachricht, die WhatsApp nicht markiert, enthält keine ID, und die Auflösung kann auch fehlschlagen. Für eine zuverlässige Zuordnung verwenden Sie explizite interaktive Antwort-Identifier zusammen mit dem gespeicherten Konversations- oder Aufgabenstatus Ihrer Anwendung. Ausgehendes metadata bleibt auf dem ausgehenden Datensatz und wird nicht automatisch auf die Antwort kopiert.
Das Zitat wird aufgelöst, bevor der Send angenommen wird, sodass ein Zitat, das nicht gerendert werden kann, den Request selbst fehlschlagen lässt und nichts erstellt oder berechnet wird. Eine ID, die keine Nachricht dieses Workspace benennt, oder eine, die älter als die 15 Tage ist, in denen eine Nachricht zitierbar bleibt, gibt einen 404 E15071 WhatsAppReferencedMessageNotFound zurück. Eine, die eine Nachricht benennt, die WhatsApp nie erreicht hat, oder eine Nachricht aus einer anderen Konversation als to und from dieses Sends, gibt einen 422 E15072 WhatsAppMessageNotQuotable zurück. Wenn Bird den Store nicht erreichen kann, der die Frage beantwortet, gibt der Send einen 503 E15073 WhatsAppMessageLookupUnavailable zurück, bei dem es sich lohnt, es erneut zu versuchen. Zitieren funktioniert bei einem Template-Send und einem Freitext-Send gleichermaßen.
Codebeispiel
{
  "to": "+16505551234",
  "from": "+13124495648",
  "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
  "text": { "body": "Yes, that slot is still free." }
}

Tags und Metadaten

Zwei optionale Felder hängen Ihren eigenen Kontext an eine Nachricht an; beide werden bei API-Lesevorgängen zurückgegeben und sind in jedem Webhook-Event für die Nachricht enthalten:
  • tags: Bis zu 20 strukturierte { "name": ..., "value": ... }-Labels für Dimensionen mit niedriger Kardinalität, nach denen Sie filtern und berichten (eine Kampagne, eine Experimentvariante). Namen und Werte akzeptieren ASCII-Buchstaben, Ziffern, Unterstrich und Bindestrich; Namen sind auf 32 Zeichen begrenzt und innerhalb eines Sends eindeutig, Werte auf 64. Filtern Sie die Nachrichtenliste nach Tag (?tag=campaign oder ?tag=campaign:launch-week), und die Seite Metriken schlüsselt die Zustellung nach Tag auf.
  • metadata: Ein beliebiges JSON-Objekt, bis zu 2 KB serialisiert, für sendespezifischen Kontext, den Sie nicht als Filterdimension benötigen (eine interne Bestell-ID, eine Session-Referenz).
Codebeispiel
{
  "tags": [{ "name": "campaign", "value": "order-confirmations" }],
  "metadata": { "order_id": "ord_8271" }
}

Feldreferenz

FeldTypErforderlichLimits / Hinweise
tostringjaEin Empfänger pro Nachricht: eine E.164-Telefonnummer, eine geschäftsbezogene Benutzer-ID, die kein Einmalpasscode-Template akzeptiert, oder eine WhatsApp-Gruppen-ID (wag_…), die an jeden Teilnehmer dieser Gruppe sendet
fromstring (E.164)nein**Weglassen bei einem von Bird verwalteten Template, das seinen eigenen Absender wählt, und bei einem Gruppenversand, der die Nummer der Gruppe verwendet; erforderlich für eine Service-Nachricht und für ein Template, das Ihr Workspace erstellt hat, und muss eine Nummer sein, die Ihr Workspace besitzt
template.slugstringnein**Ein Template-Slug, den Ihr Workspace senden kann; von Bird verwaltete Slugs beginnen mit bird_
template.languagestringnein*Sprach-Tag des Templates (en, pt-BR); weglassen, um die Standardsprache des Templates zu senden
template.componentsarrayneinFüllt die Variablen des Templates; Komponenten-type ist body oder button
template.components[].parameters[].namestringnein†Der Platzhalter, den dieser Wert füllt, zum Beispiel ref; erforderlich und muss den deklarierten Namen des Templates entsprechen bei einem Template mit benannten Parametern, weggelassen bei einem positionellen
interactiveobjectnein**Body-Text plus ein Typ antippbaren Inhalts; eine Service-Nachricht, benötigt also ein offenes Service-Fenster. Siehe Interaktive Nachrichten
in_reply_to_message_idstringneinEine WhatsApp-Nachrichten-ID, die dieser Workspace hält, zitiert in der von Ihnen gesendeten Nachricht; wird bei Lesevorgängen zurückgegeben. Siehe Eine Nachricht zitieren
tagsarrayneinBis zu 20 {name, value}-Labels; Name ≤ 32 Zeichen, Wert ≤ 64, Namen eindeutig
metadataobjectneinBeliebiges JSON, bis zu 2 KB serialisiert
* language ist optional; ohne Angabe wird die Standardsprache des Templates gesendet. † name ist bei jedem Parameter für ein Template mit benannten Parametern erforderlich. Bei einem positionellen Template weglassen. Siehe Komponenten und Parameter. ** Übergeben Sie genau eines von template oder einem Service-Nachrichten-Inhaltsfeld (text, image, video, audio, sticker, document, location, interactive); siehe Service-Nachrichten.

Das asynchrone Modell: Was 202 bedeutet

Ein erfolgreicher Send gibt 202 Accepted mit einer Nachrichten-ID und status: accepted zurück. Der 202 wird erst zurückgegeben, nachdem der Send dauerhaft angenommen wurde; er wird nie angenommen und dann stillschweigend verworfen. Harte Fehler, die Sie beheben können, schlagen sofort mit einem 422 fehl: ein ungültiger Empfänger, ein unbekannter Template-Slug oder eine unbekannte Sprache, eine Parameter-Diskrepanz oder eine Service-Nachricht in ein geschlossenes Kundenservice-Fenster (WhatsAppServiceWindowClosed). Ein nicht gedecktes Wallet gehört nicht dazu: Der Send wird angenommen, und die Nachricht endet rejected mit insufficient_balance, sobald Bird versucht, sie zu belasten. Die tatsächliche Zustellung erfolgt asynchron: Die Nachricht wechselt zu sent, wenn wir sie an WhatsApp übergeben, dann zu einem Endstatus (delivered oder failed), wenn die Quittung eintrifft, gemeldet über Events, Webhooks und die Lese-Endpoints. Eine Lesebestätigung wird separat als read_at-Zeitstempel und whatsapp.read-Event dargestellt, nicht als Status.
Ein Datenschutzhinweis: Bei Templates der Kategorie authentication gibt der API die ausgefüllten Werte nie zurück. Das 202-Echo und jeder spätere Lesevorgang enthalten ein leeres components-Array für diese Nachrichten, sodass ein Bestätigungscode nie wieder auftaucht.

Sicher erneut versuchen

Senden Sie den Idempotency-Key-Header mit einem eindeutigen Wert pro logischem Send, und Wiederholungen werden sicher. Wenn Ihr erster Request erfolgreich war, Sie die Antwort aber nie gesehen haben (Timeout, abgebrochene Verbindung), gibt das Wiederholen mit demselben Schlüssel das ursprüngliche Ergebnis zurück, statt eine doppelte Nachricht zu senden und zu berechnen. Die wiederholte Antwort enthält einen Idempotency-Replay-Header. Siehe Idempotenz für Schlüsselformat und Aufbewahrung.

Die Antwort empfangen

Eingehende Nachrichten landen auf derselben Ressource wie ausgehende, und jede davon setzt das Service-Fenster zurück. WhatsApp-Nachrichten empfangen behandelt das Lesen über die API, das Abrufen der Medien, die ein Kontakt gesendet hat, und den whatsapp.received-Webhook.

Kosten und Abrechnung

WhatsApp wird pro Nachricht bepreist, basierend auf der Kategorie des Templates und dem Land des Empfängers; siehe WhatsApp-Preise. Eine Nachricht wird in zwei Schritten zu zwei verschiedenen Zeitpunkten berechnet, und das cost-Objekt auf der Nachricht meldet beides:
FeldBeschreibungWann es anfällt
transaction_amountGebühr von Bird für die Verarbeitung des SendsWenn Bird den angenommenen Send verarbeitet, vor dem Dispatch
passthrough_amountMetas Anteil am Nachrichtenpreis, den Bird durchreichtWenn eine zutreffende delivered- oder read-Quittung eintrifft
amountDie Summe der bisher bepreisten KomponentenWächst mit jeder eintreffenden Komponente
currency_codeDie Währung des Wallets Ihrer Organisation, von beiden Komponenten geteiltMit der ersten Komponente
Beide Beträge sind Dezimalstrings, netto ohne Steuer.
Note: a service message carries no Meta share until September 30th 2026, and neither does a utility template delivered inside an open customer service window. From October 1st 2026 Meta charges for both, except inside a 72-hour free entry point window, and with 1,000 free service messages per business phone number per month: October 2026 pricing changes.
Die beiden Komponenten werden auf unterschiedlichen Grundlagen bepreist. Die Gebühr von Bird nutzt die Kategorie des gesendeten Templates und das Land des Empfängers, das sich aus der Ländervorwahl der Telefonnummer ergibt oder, bei einem Send an eine geschäftsbezogene Benutzer-ID, aus dem zweistelligen Präfix dieser ID. Metas Anteil nutzt die Kategorie, die Meta selbst auf der zutreffenden Quittung meldet, was von der des Templates abweichen kann: Meta kann authentication-international melden, wenn seine Ziel-, Geschäftsstandort- und Berechtigungsregeln greifen. Siehe WhatsApp authentication-international-Tarife.
Was cost anzeigt, hängt davon ab, wie weit die Nachricht fortgeschritten ist:
  • Beim 202 ist cost null. Nichts wurde bepreist.
  • Nach der Verarbeitung ist transaction_amount gesetzt und amount entspricht ihm. passthrough_amount bleibt null.
  • Nach einer zutreffenden delivered- oder read-Quittung füllt eine erfolgreich erfasste Meta-Gebühr passthrough_amount, und amount spiegelt die erfassten Komponenten wider.
Eine null-Komponente bedeutet, dass kein Betrag in dieser Projektion erfasst ist; sie ist kein Beweis dafür, dass die Nachricht kostenlos war. Eine explizit mit null bepreiste Komponente zeigt "0.00000" an.
Die beiden Gebühren schlagen auch unterschiedlich fehl. Die Gebühr von Bird schlägt geschlossen fehl: Wenn sie nach dem 202 nicht durchgeht, weil das Wallet den Send nicht decken kann oder die Route keinen konfigurierten Preis hat, endet die Nachricht rejected mit dem Fehlercode insufficient_balance oder price_not_found, und nichts wird berechnet. Eine rejected-Nachricht hat WhatsApp nie erreicht, was sie von failed unterscheidet. Metas Anteil schlägt offen fehl: Wenn das Wallet nicht gedeckt ist oder der Tarif beim Eintreffen der Quittung fehlt, wird die Gebühr übersprungen, ohne den beobachteten Nachrichtenstatus rückgängig zu machen. Ihre Zustellung wird nie durch die zweite Gebühr aufgehalten.
Eine von Bird berechnete Nachricht behält diese ausgehende Gebühr, auch wenn die Zustellung später fehlschlägt. Die Meta-Gebühr wird aus einem zutreffenden delivered- oder read-Callback verarbeitet, wenn Meta reguläre Bepreisung mit einer auflösbaren Kategorie und einem auflösbaren Ziel meldet. Beide Callback-Pfade nutzen dieselbe Gebührenidentität und stützen sich auf die Deduplizierung des Billing-Service. Gleichen Sie wiederholte Quittungen mit den Abrechnungsdatensätzen ab, anstatt die Nachrichtenprojektion als permanenten Belastungsbeleg zu behandeln. Service- oder Free-Entry-Bepreisung kann die Meta-Komponente auf null setzen; eine unaufgelöste Komponente ist kein Beweis dafür, dass die Nachricht kostenlos war.
Verwenden Sie das Billing-Ledger für die finanzielle Abstimmung. cost-Felder von Nachrichten sind Projektionen der Gebühren und können verzögert oder unvollständig sein. Siehe WhatsApp-Metriken für die Unterscheidung zwischen Nachrichtenbeobachtungen und Abrechnungsdatensätzen.
WhatsApp-Events enthalten keine Kosten. Um eine der beiden Komponenten zu lesen, rufen Sie die Nachricht mit GET /v1/whatsapp/messages/{id} erneut ab.

Nächste Schritte