# 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](/docs/guides/whatsapp/message-types/interactive/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:

<!-- bird:tabs typescript,python,go,php,cli,curl -->

```typescript
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);
```

```python
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)
```

```go
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)
}
```

```php
$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();
```

```cli
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
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."
    }
  }'
```

<!-- /bird:tabs -->

`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](/docs/guides/whatsapp/message-types/interactive#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:

```json
{
  "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](/docs/guides/whatsapp/message-types/interactive#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](/docs/guides/whatsapp/message-types/interactive/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](/docs/guides/whatsapp/message-types/interactive#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](/docs/guides/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](/docs/guides/whatsapp/message-types#the-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](/docs/api/errors/E15057) and [E15058](/docs/api/errors/E15058), both on `in_reply_to_message_id` when a quote doesn't resolve or can't be quoted; see the hub's [errors](/docs/guides/whatsapp/message-types/interactive#errors) for the full interactive error table and [How sending works](/docs/guides/whatsapp/sending-whatsapp) for the errors any WhatsApp send can hit.

## Next steps

- [WhatsApp interactive messages](/docs/guides/whatsapp/message-types/interactive): what all six interactive types share
- [Location requests](/docs/guides/whatsapp/message-types/interactive/location-requests): ask for a location instead of a phone number
- [WhatsApp templates](/docs/guides/whatsapp/templates): reach a recipient whose customer service window is closed
- [How sending works](/docs/guides/whatsapp/sending-whatsapp): the request envelope, the `202` model, and safe retries