WhatsApp contact info requests
A contact info request puts one button under a WhatsApp message that asks the recipient to share a phone number. Use it when you need a number to reach someone on, such as a callback or a booking confirmation, rather than a saved address. For a location instead, use location requests.
Send a contact info request
Set interactive.type to request_contact_info, with a body_text and nothing else. WhatsApp renders the button itself, so there is nothing to label it with:
const msg = await bird.whatsapp.send({
to: "+16505551234",
from: "+13124495648",
interactive: {
type: "request_contact_info",
body_text:
"To confirm your booking we need a number to reach you on. Tap below to share yours.",
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+16505551234",
from_="+13124495648",
interactive={
"type": "request_contact_info",
"body_text": "To confirm your booking we need a number to reach you on. Tap below to share yours.",
},
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+16505551234",
From: "+13124495648",
Interactive: &bird.WhatsAppInteractiveSend{
Type: "request_contact_info",
BodyText: "To confirm your booking we need a number to reach you on. Tap below to share yours.",
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$interactive = (new WhatsAppMessageSendRequestInteractive())
->setType('request_contact_info')
->setBodyText('To confirm your booking we need a number to reach you on. Tap below to share yours.');
$message = $bird->whatsapp->send(
to: '+16505551234',
from: '+13124495648',
interactive: $interactive,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--to +16505551234 \
--from +13124495648 \
--interactive '{"type":"request_contact_info","body_text":"To confirm your booking we need a number to reach you on. Tap below to share yours."}'curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+16505551234",
"from": "+13124495648",
"interactive": {
"type": "request_contact_info",
"body_text": "To confirm your booking we need a number to reach you on. Tap below to share yours."
}
}'from is required on every service message: a number your workspace owns, not a Bird-managed one. This type names no field of its own, and the schema bars a header, a footer_text, and every other type's field (buttons, list, cta_url, cards) outright, so body_text is the whole message, capped at 1024 characters. Meta states no body-length limit for this type at all; Bird applies the 1024-character bound every other interactive type but a list menu carries.
in_reply_to_message_id still works on this type, to quote an earlier message in the same conversation. See the hub's quoting a message to correlate a reply for how resolution works and what it can miss.
Reading the shared contact
A tap does not produce an interactive_reply. It arrives as an ordinary inbound message carrying a contact_cards array:
Codebeispiel
{
"id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
"direction": "inbound",
"from": { "phone_number": "+16505551234", "bsuid": "US.13491208655302741918" },
"to": { "phone_number": "+13124495648" },
"status": "received",
"contact_cards": [
{
"origin": "contact_request",
"phone_numbers": [{ "phone_number": "+14155550829", "type": "cell" }]
}
],
"created_at": "2026-08-26T10:00:00Z"
}contact_cards is an array, read-only, and inbound only: a contacts message that carried no card reads back as [], not as an absent field. Checking origin is mandatory before treating a card as your answer. origin is contact_request when the card answers this ask, or other when the contact shared a card unprompted, which may name a third party entirely and not the contact at all. A tap carries only phone_numbers[].{phone_number, type} and omits vcard; the full contact object, with name, org, birthday, and the rest, arrives only on origin: "other". You see this reply through the message list or GET /v1/whatsapp/messages/{id}; see the hub's reading a reply for that path in full.
Correlating the answer with the question
Unlike a location request, Meta puts no context on this type's reply, so in_reply_to_message_id is omitted rather than resolved. Correlate on from plus a recent send of your own, or accept that you cannot. Two outstanding asks to the same contact are indistinguishable: nothing on the reply names which ask it answers, so a workspace that sends a second contact info request before the first is answered cannot tell which card responds to which.
This is the deliberate contrast with location requests: that type's reply carries Meta's own context, so in_reply_to_message_id resolves and the hub's quoting a message to correlate a reply mechanism links the answer back on its own. A contact info request's reply has no such mechanism to lean on.
Asking inside a template instead
The request_contact_info interactive message is the free-form counterpart of the REQUEST_CONTACT_INFO template button, which asks for the same contact card but can reach a recipient whose customer service window is closed. Use the interactive message when the recipient messaged you recently and you want the ask worded for this conversation; use the template button when the window is closed, or when the ask rides on a message you already send as a template. See WhatsApp templates for sending with a template.
Things to watch
- The customer service window has to be open. A contact info request is a service message, deliverable only inside an open window; see the hub's customer service window. The window check fails open, so a 202 is not proof the window was actually open when the send goes out.
- from must be a number your workspace owns, and the window it needs open is keyed to that number, not to your workspace as a whole.
- The reply cannot be tied to the ask by id. No context on Meta's side means in_reply_to_message_id is omitted on the reply; correlate on from plus a recent send of your own.
- A decline is silent. WhatsApp shows the recipient a share sheet, and dismissing it produces no message and no webhook at all. The absence of a contact_cards message is the only signal, so any flow that waits on an answer needs its own timeout rather than a decline event to watch for.
- No header, no footer, and no button label. The schema bars a header and footer_text on this type outright, and there is no field to label the button with. Everything the recipient reads has to be in body_text.
- The disclosed number is not guaranteed to be the one the contact chats from. Meta warns that a user's ID and phone number may not always match, so do not assume the shared number equals from.phone_number. It is also not guaranteed to be E.164: Bird normalizes it where parseable and passes it through verbatim where not.
- The reply is a contact_cards message, not an interactive_reply. An integration that only watches interactive_reply for a tap will miss this type entirely, and so will one that only watches inbound location for the other request type.
Everything the schema can express here, an over-long body_text, a header, a footer_text, or any of buttons, list, cta_url, cards, is a plain request-validation failure with no catalog code. The one interactive-specific pair that can fire on this type is E15057 and E15058, both on in_reply_to_message_id when a quote doesn't resolve or can't be quoted; see the hub's errors for the full interactive error table and How sending works for the errors any WhatsApp send can hit.
Next steps
- WhatsApp interactive messages: what all six interactive types share
- Location requests: ask for a location instead of a phone number
- WhatsApp templates: reach a recipient whose customer service window is closed
- How sending works: the request envelope, the 202 model, and safe retries