Invio di messaggi WhatsApp
Questa guida tratta l'endpoint di invio, POST /v1/whatsapp/messages. Si costruisce un singolo payload JSON con un destinatario e esattamente un tipo di contenuto: un template pre-approvato oppure un messaggio di servizio contenente testo, un'immagine, video, audio, uno sticker, un documento, una posizione, schede contatto o qualcosa da toccare. Bird restituisce 202 Accepted con un ID messaggio e consegna in modo asincrono. Quale dei due puoi inviare dipende dalla finestra di servizio clienti. Ogni richiesta invia un messaggio a un destinatario, e non esiste un endpoint batch.
Un invio minimale
Il payload valido più piccolo è un destinatario to e un template con il suo slug. Aggiungi language se vuoi una lingua specifica; ometterlo invia la lingua predefinita del template. Compila le eventuali variabili dichiarate dal template tramite components.
La chiamata curl indica l'host US; se la tua chiave inizia con bk_eu1_, chiama invece https://eu1.platform.bird.com. Gli SDK leggono la regione dalla chiave, quindi non impostano alcun 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);msg = client.whatsapp.send(
to="+31612345678",
template="bird_otp",
language="en",
components=[{"type": "body", "parameters": [{"type": "text", "text": "123456"}]}],
)
print(msg.id, msg.status)code := "123456"
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+15551234567",
Template: "bird_otp",
Language: "en",
Components: []bird.WhatsAppMessageTemplateComponent{{
Type: "body",
Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "text", Text: &code}},
}},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)$message = $bird->whatsapp->send(
to: '+15551234567',
template: 'bird_otp',
language: 'en',
components: [
(new WhatsAppMessageTemplateComponent())
->setType('body')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setText('123456'),
]),
],
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--components '[{"parameters":[{"text":"1234","type":"text"}],"type":"body"},{"parameters":[{"text":"1234","type":"text"}],"type":"button"}]' \
--language en \
--template bird_otp \
--to +31612345678curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+31612345678",
"template": {
"slug": "bird_otp",
"language": "en",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "1234"
}
]
},
{
"type": "button",
"parameters": [
{
"type": "text",
"text": "1234"
}
]
}
]
}
}'La finestra di servizio clienti
Quale dei due puoi inviare dipende da un singolo stato: se la finestra di servizio clienti è aperta.
Il contatto apre la finestra inviando un messaggio o chiamando il tuo numero aziendale, e resta aperta per 24 ore, resettandosi ogni volta che ti scrive di nuovo. Finché è aperta puoi inviare un messaggio di servizio, cioè qualsiasi contenuto libero: testo, immagine, video, audio, sticker, documento, posizione o interattivo. Una volta scaduta, solo un template pre-approvato lo raggiunge, e la sua risposta a esso riapre la finestra.
Bird tiene traccia della finestra per te, quindi un messaggio di servizio inviato a una finestra chiusa viene rifiutato prima che qualsiasi cosa venga creata o addebitata: la richiesta restituisce un 422 E15044 WhatsAppServiceWindowClosed. Il controllo usa le informazioni disponibili e consente di proseguire se non può essere completato, quindi un 202 non è la prova che la finestra fosse effettivamente aperta al momento dell'invio; una finestra che scade tra l'accettazione e l'invio fallisce in modo asincrono, con service_window_expired sul last_error del messaggio.
Vedi la finestra di servizio clienti per il ciclo di vita completo: cosa la apre, cosa la resetta e come interagisce con i prezzi.
Costruire il payload
Destinatario
to è un singolo destinatario, indicato come numero di telefono o come user ID con ambito business. Un numero di telefono è in formato E.164: un + iniziale, prefisso internazionale e numero dell'abbonato, ad esempio +14155550100. Il numero viene validato, quindi un valore che non può essere un numero reale e componibile (lunghezza errata, prefisso non assegnato) viene rifiutato con un 422 WhatsAppInvalidRecipient prima di qualsiasi addebito. Un messaggio va a un destinatario; non esiste un array di destinatari né un invio batch, quindi per raggiungere più persone effettua una chiamata per destinatario.
Un user ID con ambito business come US.13491208655302741918 indirizza un contatto di cui non hai il numero di telefono, ed è il modo in cui rispondi a un contatto che ti ha raggiunto senza fornirne uno. Cambiano due cose: il numero di invio deve appartenere allo stesso portfolio business a cui è associato l'ID, e un template di codice di verifica monouso richiede un numero di telefono. Uno gestito da Bird viene rifiutato all'accettazione con un 422 WhatsAppRecipientNotSupportedForTemplate; un template di autenticazione creato dal tuo spazio di lavoro viene accettato e poi fallisce, poiché Meta richiede un numero di telefono per esso.
Template
template indica il template pre-approvato da inviare:
- slug (obbligatorio): lo slug del template, ad esempio bird_order_confirmation. Deve corrispondere a un template nel tuo catalogo (lettere minuscole, cifre e underscore).
- language: il tag lingua del template, ad esempio en o pt-BR. Omettilo per inviare la lingua predefinita del template; specificare una lingua che il template non possiede restituisce un 422 che elenca quelle disponibili. Il messaggio accettato riporta la lingua risolta.
- components: i valori che compilano le variabili del template (vedi Componenti e parametri). Omettilo per un template senza variabili.
Sfoglia i tuoi template, le loro lingue e un'anteprima renderizzata di ciascuno nella pagina Templates.
Componenti e parametri
I template contengono variabili, con nome ({{ref}}, {{amount}}) o numerate ({{1}}, {{2}}). Ne fornisci i valori tramite components. Ogni componente dichiara un type (body o button) e un array parameters. Ogni parametro dichiara il proprio type (text, image, video, gif, document o location) e contiene il campo corrispondente: text una stringa semplice, image/video/gif/document un url pubblico con schema https, e location un punto sulla mappa. Un template con parametri con nome richiede un name su ogni parametro, che deve corrispondere esattamente ai nomi dichiarati dal template (vedi Riferimento campi). Un template posizionale omette name e prende i valori nell'ordine {{n}}, quindi il primo parametro compila {{1}}. In entrambi i casi, parametri che non corrispondono a quanto dichiarato dal template restituiscono un 422 WhatsAppTemplateParameterMismatch. Esiste anche un tipo di componente header nel protocollo: su un template gestito da Bird viene scartato, poiché nessun template gestito da Bird dichiara una variabile header, ma su un template creato dal tuo spazio di lavoro viene inoltrato, ed è così che un template utility o marketing con header media riceve la sua immagine.
Ad esempio, un template di codice di verifica monouso il cui corpo contiene {{1}} is your verification code e il cui pulsante copia il codice prende il codice sia come parametro body sia come parametro button, in modo posizionale (senza name):
Esempio di codice
{
"components": [
{ "type": "body", "parameters": [{ "type": "text", "text": "481920" }] },
{ "type": "button", "parameters": [{ "type": "text", "text": "481920" }] }
]
}Categoria e mittente
La categoria di un template (authentication, utility o marketing) determina come WhatsApp tratta il messaggio e, insieme al paese di destinazione, quanto costa.
Chi possiede il mittente determina se devi indicarlo:
- Un template gestito da Bird (il cui slug inizia con bird_) invia dal numero che Bird riserva per quella categoria, quindi ometti from. Impostarlo restituisce un 422 WhatsAppSenderNotAllowed.
- Qualsiasi altra cosa indica il proprio mittente in from: un messaggio di servizio di qualsiasi tipo e qualsiasi template creato dal tuo spazio di lavoro. Il numero deve appartenere al tuo spazio di lavoro. Ometterlo restituisce un 422 WhatsAppSenderRequired, e un numero da cui lo spazio di lavoro non può inviare restituisce un 422 WhatsAppSenderNotFound. Un template creato deve inoltre appartenere allo stesso WhatsApp Business Account del numero, altrimenti l'invio restituisce un 422 WhatsAppSenderWABAMismatch.
Configurazione numero di telefono copre entrambi i tipi di numero e come viene collegato un numero proprio.
Messaggi di servizio
Invece di template, includi esattamente uno tra text, image, video, audio, sticker, document, location, contact_cards o interactive. Tutti e nove sono messaggi di servizio, quindi richiedono una finestra di servizio clienti aperta. Ognuno di essi richiede anche from, un numero che il tuo spazio di lavoro possiede; i numeri gestiti da Bird non possono trasportarlo.
- text: { "body": "..." }, fino a 4096 caratteri. Aggiungi "preview_url": true per renderizzare un'anteprima del link per il primo URL in body.
- image, video, audio, sticker, document: ciascuno accetta un URL https pubblico che WhatsApp scarica al momento dell'invio (url), quindi un URL firmato deve rimanere valido oltre l'invio. Un URL http viene rifiutato immediatamente. WhatsApp scarica il file stesso, quindi un URL che non riesce a raggiungere, uno che serve un tipo non supportato o un file che supera il limite di dimensione per il suo tipo viene accettato e poi fallisce, con media_rejected sul last_error del messaggio e il motivo specifico di WhatsApp in description. image, video e document accettano anche un caption opzionale; document accetta anche un filename opzionale; audio accetta un flag voice opzionale per la resa come nota vocale.
- location: { "latitude": ..., "longitude": ... } (entrambi obbligatori, gradi decimali) più name e address opzionali.
- contact_cards: un array di massimo cinque contatti condivisi in un messaggio. Il name di ogni scheda richiede formatted_name più almeno un'altra parte (first_name, last_name, middle_name, prefix o suffix); phone_numbers, emails, urls e addresses accettano ciascuno fino a dieci voci, e org e birthday (come YYYY-MM-DD) sono opzionali. Un phone_number in E.164 assegna a quella scheda un pulsante che apre una chat con esso.
- interactive: testo del corpo più qualcosa da toccare, in uno dei sei tipi: pulsanti di risposta, un menu a lista, un pulsante con link, un carosello multimediale o un singolo pulsante che chiede al destinatario la propria posizione o il proprio numero di telefono. Messaggi interattivi copre la struttura wire di ogni tipo, le risposte che un tocco produce e i limiti.
Esempio di codice
{
"to": "+16505551234",
"from": "+13124495648",
"text": { "body": "Your order shipped: https://example.com/track/A1B2C3", "preview_url": true }
}Una richiesta senza contenuto, o con più di un tipo, viene rifiutata con un 422.
Citare un messaggio
Imposta in_reply_to_message_id a un ID messaggio WhatsApp per inviare il tuo messaggio come risposta ad esso, come quando si tocca rispondi nell'app WhatsApp per citare un messaggio. Il destinatario vede il tuo messaggio con quello citato sopra, e il campo viene restituito a ogni lettura del messaggio.
Funziona anche nell'altra direzione: un messaggio in entrata che WhatsApp contrassegna come risposta contiene l'ID del messaggio citato nello stesso campo, ed è così che puoi capire a quale dei tuoi messaggi risponde. Un messaggio in entrata che WhatsApp non contrassegna non contiene alcun ID, e anche la risoluzione può fallire. Per una correlazione affidabile, usa identificatori di risposta interattivi espliciti con lo stato di conversazione o attività memorizzato dalla tua applicazione. Il metadata in uscita resta sul record in uscita e non viene copiato automaticamente sulla risposta.
La citazione viene risolta prima che l'invio sia accettato, quindi una citazione che non può essere renderizzata fa fallire la richiesta stessa e nulla viene creato o addebitato. Un id che non corrisponde a nessun messaggio detenuto da questo spazio di lavoro, o più vecchio dei 15 giorni in cui un messaggio resta citabile, restituisce un 404 E15071 WhatsAppReferencedMessageNotFound. Uno che indica un messaggio che non ha mai raggiunto WhatsApp, o un messaggio da una conversazione diversa rispetto a to e from di questo invio, restituisce un 422 E15072 WhatsAppMessageNotQuotable. Se Bird non riesce a raggiungere lo store che risponde alla domanda, l'invio restituisce un 503 E15073 WhatsAppMessageLookupUnavailable, per cui vale la pena riprovare. La citazione funziona sia su un invio template sia su un invio libero.
Esempio di codice
{
"to": "+16505551234",
"from": "+13124495648",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"text": { "body": "Yes, that slot is still free." }
}Tag e metadati
Due campi opzionali allegano il tuo contesto a un messaggio; entrambi vengono restituiti nelle letture API e accompagnano ogni evento webhook per il messaggio:
- tags: fino a 20 etichette { "name": ..., "value": ... } strutturate per dimensioni a bassa cardinalità su cui filtrare e generare report (una campagna, una variante di esperimento). Nomi e valori accettano lettere ASCII, cifre, underscore e trattino; i nomi sono limitati a 32 caratteri e univoci all'interno di un invio, i valori a 64. Filtra la lista messaggi per tag (?tag=campaign o ?tag=campaign:launch-week), e la pagina Metriche suddivide le consegne per tag.
- metadata: un oggetto JSON arbitrario, fino a 2 KB serializzato, per contesto per-invio che non ti serve come dimensione di filtro (un ID ordine interno, un riferimento di sessione).
Esempio di codice
{
"tags": [{ "name": "campaign", "value": "order-confirmations" }],
"metadata": { "order_id": "ord_8271" }
}Riferimento campi
| Campo | Tipo | Obbligatorio | Limiti / note |
|---|---|---|---|
| to | string | sì | Un destinatario per messaggio: un numero di telefono E.164 oppure un user ID con ambito business, che nessun template di codice di verifica monouso accetta |
| from | string (E.164) | no** | Ometti per un template gestito da Bird, che sceglie il proprio mittente; obbligatorio per un messaggio di servizio e per un template creato dal tuo spazio di lavoro, e deve essere un numero che il tuo spazio di lavoro possiede |
| template.slug | string | no** | Uno slug di template che il tuo spazio di lavoro può inviare; gli slug gestiti da Bird iniziano con bird_ |
| template.language | string | no* | Tag lingua del template (en, pt-BR); ometti per inviare la lingua predefinita del template |
| template.components | array | no | Compila le variabili del template; il type del componente è body o button |
| template.components[].parameters[].name | string | no† | Il segnaposto che questo valore compila, ad esempio ref; obbligatorio e deve corrispondere ai nomi dichiarati dal template per un template con parametri nominali, omesso per uno posizionale |
| interactive | object | no** | Testo del corpo più un tipo di contenuto tappabile; è un messaggio di servizio, quindi richiede una finestra di servizio aperta. Vedi Messaggi interattivi |
| in_reply_to_message_id | string | no | Un ID messaggio WhatsApp detenuto da questo spazio di lavoro, citato nel messaggio che invii; restituito nelle letture. Vedi Citare un messaggio |
| tags | array | no | Fino a 20 etichette {name, value}; nome ≤ 32 caratteri, valore ≤ 64, nomi univoci |
| metadata | object | no | JSON arbitrario, fino a 2 KB serializzato |
* language è opzionale; ometterlo invia la lingua predefinita del template.
† name è obbligatorio su ogni parametro per un template con parametri nominali. Omettilo per uno posizionale. Vedi Componenti e parametri.
** Includi esattamente uno tra template o un campo di contenuto messaggio di servizio (text, image, video, audio, sticker, document, location, interactive); vedi Messaggi di servizio.
Il modello asincrono: cosa significa 202
Un invio riuscito restituisce 202 Accepted con un ID messaggio e status: accepted. Il 202 viene restituito solo dopo che l'invio è stato accettato in modo durevole; non viene mai accettato e poi scartato silenziosamente. I fallimenti definitivi risolvibili falliscono immediatamente con un 422: un destinatario non valido, uno slug o una lingua di template sconosciuti, una mancata corrispondenza di parametri o un messaggio di servizio inviato a una finestra di servizio clienti chiusa (WhatsAppServiceWindowClosed). Un wallet senza fondi non rientra tra questi: l'invio viene accettato e il messaggio termina con rejected e insufficient_balance quando Bird tenta di addebitarlo. La consegna effettiva avviene in modo asincrono: il messaggio passa a sent quando viene consegnato a WhatsApp, poi a uno stato terminale (delivered o failed) quando arriva la ricevuta, segnalato tramite eventi, webhook e gli endpoint di lettura. Una conferma di lettura viene esposta separatamente come timestamp read_at ed evento whatsapp.read anziché come stato.
Una nota sulla privacy: per i template di categoria authentication l'API non restituisce mai i valori compilati. L'eco del 202 e ogni lettura successiva riportano un array components vuoto per quei messaggi, così un codice di verifica non riemerge mai.
Riprovare in sicurezza
Invia l'header Idempotency-Key con un valore univoco per ogni invio logico, e i tentativi successivi diventano sicuri. Se la tua prima richiesta è andata a buon fine ma non hai mai visto la risposta (timeout, connessione interrotta), ripeterla con la stessa chiave restituisce il risultato originale invece di inviare, e addebitare, un messaggio duplicato. La risposta ripetuta contiene un header Idempotency-Replay. Vedi idempotenza per il formato della chiave e la durata di conservazione.
Ricevere la risposta
I messaggi in entrata si trovano sulla stessa risorsa di quelli in uscita, e ognuno di essi resetta la finestra di servizio. Ricezione di messaggi WhatsApp copre la lettura tramite API, il recupero dei media inviati da un contatto e il webhook whatsapp.received.
Costi e fatturazione
WhatsApp ha un prezzo per messaggio, basato sulla categoria del template e sul paese del destinatario; vedi Prezzi WhatsApp. Un messaggio viene addebitato in due fasi, in due momenti diversi, e l'oggetto cost sul messaggio riporta entrambi:
| Campo | Cos'è | Quando arriva |
|---|---|---|
| transaction_amount | La commissione di Bird per la gestione dell'invio | Quando Bird elabora l'invio accettato, prima del dispatch |
| passthrough_amount | La quota di Meta sul prezzo del messaggio, che Bird trasferisce | Quando arriva una ricevuta delivered o read applicabile |
| amount | La somma dei componenti prezzati finora | Cresce man mano che ogni componente arriva |
| currency_code | La valuta del wallet della tua organizzazione, condivisa da entrambi i componenti | Con il primo componente |
Entrambi gli importi sono stringhe decimali, al netto delle tasse.
I due componenti sono prezzati su input diversi. La commissione di Bird usa la categoria del template inviato e il paese del destinatario, che deriva dal prefisso internazionale del numero di telefono o, per un invio indirizzato a un user ID con ambito business, dal prefisso di due lettere su quell'ID. La quota di Meta usa la categoria che Meta stessa riporta sulla ricevuta applicabile, che può differire da quella del template: Meta può riportare authentication-international quando si applicano le sue regole su destinazione, sede del business ed eleggibilità. Vedi Tariffe authentication-international WhatsApp.
Cosa legge cost dipende da quanto è avanzato il messaggio:
- Al 202, cost è null. Nulla è stato prezzato.
- Dopo l'elaborazione, transaction_amount è impostato e amount lo eguaglia. passthrough_amount resta null.
- Dopo una ricevuta delivered o read applicabile, un addebito Meta registrato correttamente popola passthrough_amount e amount riflette i componenti registrati.
Un componente null significa che nessun importo è registrato in quella proiezione; non è la prova che il messaggio fosse gratuito. Un componente esplicitamente prezzato a zero riporta "0.00000".
I due addebiti falliscono anche in modo diverso. Se non è possibile addebitare la commissione di Bird, l'invio viene bloccato: quando non può andare a buon fine dopo il 202 perché il wallet non può coprire l'invio o la rotta non ha un prezzo configurato, il messaggio termina con rejected e codice di errore insufficient_balance o price_not_found, e nulla viene addebitato. Un messaggio rejected non ha mai raggiunto WhatsApp, ed è questo che lo distingue da failed. Un errore nell'addebito della quota di Meta non blocca la consegna: se il saldo del wallet è insufficiente o la tariffa manca quando arriva la ricevuta, l'addebito viene saltato senza invertire lo stato osservato del messaggio. La tua consegna non viene mai bloccata dal secondo addebito.
Un messaggio addebitato da Bird conserva quell'addebito in uscita anche se la consegna successivamente fallisce. La commissione Meta viene elaborata da un callback delivered o read applicabile quando Meta riporta un prezzo regolare con categoria e destinazione risolvibili. Entrambi i percorsi di callback usano la stessa identità di commissione e si affidano alla deduplicazione del servizio di fatturazione. Riconcilia le ricevute ripetute con i record di fatturazione invece di trattare la proiezione del messaggio come una ricevuta di addebito permanente. Il prezzo di servizio o a ingresso gratuito può rendere il componente Meta zero; un componente non risolto non è la prova che il messaggio fosse gratuito.
Usa il registro di fatturazione per la riconciliazione finanziaria. I campi cost dei messaggi sono proiezioni degli addebiti e possono essere in ritardo o rimanere incompleti. Vedi Metriche WhatsApp per la distinzione tra osservazioni dei messaggi e record di fatturazione.
Gli eventi WhatsApp non riportano i costi. Per leggere uno dei due componenti, rileggi il messaggio con GET /v1/whatsapp/messages/{id}.
Prossimi passi
- Messaggi di servizio: i nove tipi di contenuto libero e cosa accetta ciascuno
- Ricezione di messaggi WhatsApp: messaggi in entrata, media e il webhook whatsapp.received
- User ID con ambito business: indirizzare un contatto che ti ha raggiunto senza un numero di telefono
- Templates: sfoglia il catalogo e leggi le variabili di un template
- Idempotenza: come riprovare in sicurezza con l'header Idempotency-Key
Risorse correlate
Prosegui con la documentazione, le guide e gli esempi per questo argomento. Le risorse sono in inglese.
Guarda la guidaConnecting WhatsApp to Bird: from buying a number to a live channelComprendi il concettoWhat is the 24-hour customer service window on WhatsApp?Usa lo strumentoWhatsApp message builderEsplora la funzionalitàWhatsApp
Prova l'esercitazione e ottieni un brief di implementazione