Sign inGet started

Recebendo cartões de contato do WhatsApp

contact_cards é o único recurso que carrega o mesmo campo nas duas direções. Um contato pode compartilhar um cartão da agenda dele, e um toque em uma solicitação de informações de contato que você enviou também chega aqui, trazendo o número que ele escolheu divulgar.

O que um cartão de contato recebido carrega

contact_cards é sempre um array, e origin indica como o cartão chegou:
Exemplo de código
{
  "id": "wam_01kyg9v3timy1w7n0r4cbh9ukf",
  "direction": "inbound",
  "from": { "phone_number": "+14155550100" },
  "to": { "phone_number": "+13124495569" },
  "status": "received",
  "contact_cards": [
    {
      "origin": "contact_request",
      "phone_numbers": [{ "phone_number": "+14155550100", "type": "cell" }]
    }
  ],
  "created_at": "2026-08-25T09:27:45Z"
}
originComo o cartão chegou
contact_requestO contato tocou em um botão que você enviou pedindo o número dele
otherO contato compartilhou um cartão no chat, espontaneamente
Verifique origin antes de tratar um cartão como resposta à sua solicitação. É o único sinal que diferencia os dois, e um cartão compartilhado espontaneamente pode conter o nome de um terceiro em vez do contato. A lista de valores é aberta, então trate qualquer valor que você não reconheça como uma forma de compartilhamento adicionada posteriormente.
Um cartão que este espaço de trabalho enviou aparece na leitura sem nenhum origin, e é assim que um cartão enviado se diferencia de um recebido no mesmo campo.

O que um toque carrega e o que um cartão compartilhado carrega

Os dois chegam com quantidades diferentes de detalhe, e nada em um cartão é obrigatório: WhatsApp envia as partes que o cartão contém e omite o restante, então um cartão que contém apenas um origin ainda chega em vez de ser descartado.
CampoEm um toque de botãoEm um cartão compartilhado no chat
phone_numbers[].phone_number, typeO número que o contato escolheu divulgarOs números que o cartão contém
vcardOmitido; um toque carrega apenas o númeroO cartão em formato vCard
name, org, birthday, emails, urls, addressesO que WhatsApp envia, que normalmente é nadaPresentes quando o cartão os contém
Exemplo de código
{
  "contact_cards": [
    {
      "origin": "other",
      "vcard": "BEGIN:VCARD\nVERSION:3.0\nN:Johnson;Barbara;;;\nTEL;type=CELL:+16505551234\nEND:VCARD\n",
      "name": {
        "formatted_name": "Barbara J. Johnson",
        "first_name": "Barbara",
        "last_name": "Johnson"
      },
      "org": { "company": "Northside Plumbing" },
      "phone_numbers": [{ "phone_number": "+16505551234", "type": "cell" }]
    }
  ]
}
Dois campos exigem cuidado ao fazer o parsing. phone_number é normalizado para E.164 quando pode ser interpretado, e repassado exatamente como o dispositivo do contato o armazenou quando não pode, incluindo ramais, então faça o parsing de forma defensiva em vez de assumir E.164. birthday vem do dispositivo sem validação e é repassado como texto no formato YYYY-MM-DD em vez de ser tipado como data, então não assuma que ele pode ser interpretado. Um rótulo type em um cartão recebido vem em letras minúsculas, e WhatsApp não define um vocabulário para ele, então faça a comparação sem distinguir maiúsculas e minúsculas em vez de usar switch em CELL.

O número de telefone que um contato divulga

Um contato que adotou um nome de usuário do WhatsApp chega até você por ID de usuário com escopo de negócio sem número de telefone em from. Uma solicitação de informações de contato é como você pede o número, e este recurso é onde a resposta chega, com origin: "contact_request" e o número em phone_numbers.
O número divulgado não é garantido como sendo o número de onde o contato conversa: a Meta alerta que o identificador e o número de telefone de um usuário podem não coincidir, então armazene o número divulgado como um dado próprio em vez de sobrescrever a identidade em from.

O payload do webhook

whatsapp.received carrega o array contact_cards no envelope do evento:
Exemplo de código
{
  "type": "whatsapp.received",
  "timestamp": "2026-08-25T09:27:45.019Z",
  "data": {
    "whatsapp_id": "wam_01kyg9v3timy1w7n0r4cbh9ukf",
    "workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
    "direction": "inbound",
    "from": { "phone_number": "+14155550100", "bsuid": "US.13491208655302741918" },
    "to": { "phone_number": "+13124495569" },
    "contact_cards": [
      {
        "origin": "contact_request",
        "phone_numbers": [{ "phone_number": "+14155550100", "type": "cell" }]
      }
    ],
    "tags": null,
    "metadata": null
  }
}

Pontos de atenção

  • Uma solicitação recusada não produz nada. WhatsApp mostra ao contato uma tela de compartilhamento, e descartá-la não envia mensagem nem dispara webhook, então um fluxo que espera um número precisa do seu próprio timeout em vez de um evento de recusa para monitorar.
  • Duas solicitações pendentes são indistinguíveis. Um cartão que responde a uma solicitação de informações de contato não carrega in_reply_to_message_id, então uma segunda solicitação enviada antes de a primeira ser respondida não pode ser associada à sua própria resposta.
  • O array pode conter vários cartões. Um contato que compartilha múltiplos cartões em uma mensagem preenche várias entradas, cada uma com seu próprio origin.
  • Um cartão é dado de contato que você não coletou. Ele pode conter nome, números e data de nascimento de terceiros, então aplique as mesmas regras de retenção e consentimento que você aplicaria a qualquer outro dado pessoal antes de armazená-lo.

Próximos passos