Phones (canais)
Phones representam cada conexão WhatsApp ou Instagram da sua account. São o pré-requisito para enviar/receber qualquer mensagem.
Base path: /v1/phones
Objeto Phone
{
"id": "5f7b2e1c-3d4a-4e5f-9a8b-1c2d3e4f5a6b",
"accountId": "a1b2c3d4-...",
"type": "BAILEYS",
"label": "Atendimento Vendas",
"phoneNumber": "5511999998888",
"status": "CONNECTED",
"webhookUrl": "https://api.minhaempresa.com/wpp-events",
"webhookEvents": ["message:received", "message:status"],
"createdAt": "2026-05-21T14:00:00.000Z",
"updatedAt": "2026-05-21T14:02:30.000Z"
}
Campos comuns
| Campo | Tipo | Descrição |
|---|---|---|
id | UUID | Identificador local (use em todos os endpoints) |
type | enum | BAILEYS, WABA, INSTAGRAM (imutável) |
label | string | Nome legível (1-120 chars) |
phoneNumber | string | null | E.164 sem +, populado após conexão |
status | enum | PENDING, PENDING_QR, PENDING_WEBHOOK, CONNECTING, CONNECTED, DISCONNECTED, TOKEN_EXPIRED, FAILED |
webhookUrl | string | null | Onde o gateway entrega eventos |
webhookEvents | string[] | Lista de eventos subscritos (vazio = todos) |
disconnectedAt | datetime | null | Quando saiu de CONNECTED |
Campos exclusivos WABA
wabaPhoneNumberId, wabaBusinessAccountId, wabaThroughputLevel, wabaQualityRating, wabaWebhookConfigured, wabaCoexistence, wabaContactsSyncAt, wabaHistorySyncAt. Tokens (wabaAccessTokenEnc) são internos — nunca retornados.
Campos exclusivos Instagram
igUserId, igUsername, igAccountType, igTokenExpiresAt, igWebhookConfigured.
Endpoints
POST /v1/phones
Cria um novo phone. Para BAILEYS, inicia geração do QR. Para WABA, o Worker valida as credenciais e hidrata a metadata em seguida, de forma assíncrona: o phone nasce PENDING_WEBHOOK, e token recusado pela Meta o deixa em TOKEN_EXPIRED. Para INSTAGRAM, use o fluxo OAuth dedicado.
Headers
| Header | Valor | Obrigatório |
|---|---|---|
X-API-Key | ak_live_... | sim |
Content-Type | application/json | sim |
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | enum | sim | BAILEYS ou WABA (Instagram usa endpoint próprio) |
label | string | sim | 1-120 chars |
webhook.url | URL | não | Endpoint HTTPS pra eventos |
webhook.secret | string | não | Secret HMAC (≥16 chars; obrigatório se url presente) |
webhook.events | string[] | não | Lista de tipos (vazio = todos) |
credentials.phoneNumberId | string | só WABA | Phone Number ID do Meta |
credentials.businessAccountId | string | só WABA | WABA ID |
credentials.accessToken | string | só WABA | System User token longo |
credentials.verifyToken | string | não | Token de verificação webhook |
Exemplo Baileys:
curl -X POST https://wpp.ogmma.com.br/v1/phones \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "BAILEYS",
"label": "Atendimento Vendas",
"webhook": {
"url": "https://api.acme.com/wpp",
"secret": "supersecret-pelomenos-16chars",
"events": ["message:received", "channel:connected"]
}
}'
Exemplo WABA manual (credenciais via Meta Business Manager):
curl -X POST https://wpp.ogmma.com.br/v1/phones \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "WABA",
"label": "Comercial Oficial",
"credentials": {
"phoneNumberId": "123456789012345",
"businessAccountId": "987654321098765",
"accessToken": "EAAxxx...",
"verifyToken": "meu-verify-token"
},
"webhook": { "url": "https://api.acme.com/wpp", "secret": "supersecret-pelomenos-16chars" }
}'
WABA por credenciais é o caminho legado. Conexão nova deve usar o Embedded Signup (
POST /v1/phones/waba/embedded-signup): este endpoint grava as credenciais sem ler a Meta e sem aplicar a política de coexistência — o canal nasce marcado como dedicado. Por isso, quando otypeéWABA, a resposta trazDeprecation: trueeLink: </v1/phones/waba/embedded-signup>; rel="successor-version". CriarBAILEYSpor esta rota é o caminho normal e não leva esses headers.
Response 201
{
"id": "5f7b2e1c-...",
"type": "BAILEYS",
"status": "PENDING_QR",
"label": "Atendimento Vendas",
"webhookUrl": "https://api.acme.com/wpp",
"createdAt": "2026-05-21T14:00:00.000Z"
}
Errors
| HTTP | Code | Quando |
|---|---|---|
| 400 | VALIDATION_ERROR | Campo inválido |
| 400 | WABA_CREDENTIALS_REQUIRED | type=WABA sem credentials |
| 401 | AUTH_MISSING | Sem X-API-Key |
| 402 | ACCOUNT_SUSPENDED | Account em inadimplência |
GET /v1/phones
Lista todos os phones da account, ordenados por createdAt desc.
curl https://wpp.ogmma.com.br/v1/phones \
-H "X-API-Key: ak_live_..."
Response 200
[
{ "id": "...", "type": "BAILEYS", "status": "CONNECTED", ... },
{ "id": "...", "type": "WABA", "status": "CONNECTED", ... }
]
GET /v1/phones/:id
Detalhe de um phone específico.
curl https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-... \
-H "X-API-Key: ak_live_..."
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | PHONE_NOT_FOUND | ID inexistente ou pertence a outra account |
DELETE /v1/phones/:id
Remove um phone. Worker é notificado para encerrar a sessão (Baileys envia logout). Operação destrutiva e irreversível — auth state e mídia local são apagados; mensagens enviadas (messages_sent) ficam para auditoria.
curl -X DELETE https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-... \
-H "X-API-Key: ak_live_..."
Query
| Param | Tipo | Default | Descrição |
|---|---|---|---|
deregister | boolean | false | Tira o número da Cloud API na Meta. Irreversível — voltar exige novo /register com PIN. Ignorado em número coexistente (a Meta proíbe) |
Response 204 (sem body) — nada ficou pendente do lado da Meta.
Response 200 — sobrou uma ação que só o dono pode fazer:
{
"deleted": true,
"pending": {
"action": "DISCONNECT_IN_WHATSAPP_BUSINESS_APP",
"reason": "Número em coexistência: a Meta não permite desregistrar pela API. ..."
}
}
O phone é apagado nos dois casos. O que muda é o que acontece na Meta:
| Tipo | O que o gateway faz | Sobra algo? |
|---|---|---|
| Baileys | logout da sessão | não |
| WABA dedicado | unsubscribe da WABA (só se era o último número dela) + deregister se ?deregister=true | não, quando deregister=true |
| WABA coexistente | unsubscribe da WABA (só se era o último número dela) | sim — soltar o companion da Cloud API é ação do dono, no celular: Ajustes › Conta › Plataforma Empresarial › Desconectar conta |
A Meta proíbe o
deregisterem número que está ao mesmo tempo na Cloud API e no WhatsApp Business app. Não é limitação do gateway.
unsubscribeé da WABA inteira, não do número (DELETE /{wabaId}/subscribed_apps). Uma WABA hospeda vários números; por isso o gateway só cancela a inscrição quando o phone apagado era o último daquela WABA. Enquanto sobrar outro, a inscrição fica de pé e os demais números continuam recebendo webhook.
deregisteré opt-in. Apagar um canal para recriá-lo (trocar de account, refazer webhook, corrigir label) é caso comum, e umderegistersilencioso obrigaria a um novo/registercom o PIN de 2FA. Passe?deregister=truequando a intenção for mesmo soltar o número da Cloud API.
PATCH /v1/phones/:id
Atualiza apenas a configuração de webhook do phone. Não permite trocar type ou label.
O bloco
webhooké tudo-ou-nada. Campo omitido dentro dele é apagado, não preservado — mandar{webhook:{secret}}zeraurleevents. Eurlsemsecreté recusado com400: o dispatcher exige os dois para entregar, então esse estado produzia canal que parece configurado e não entrega nada. Mande sempre o bloco completo.
Body
| Campo | Tipo | Descrição |
|---|---|---|
webhook.url | URL | Omitido remove o webhook. null é recusado com 400 — para remover, omita |
webhook.secret | string | ≥16 chars |
webhook.events | string[] | Lista de tipos |
sharedWebhookId | UUID | null | Aponta para um SharedWebhook (futura feature multi-phone). Omitido ou null desvincula |
curl -X PATCH https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-... \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "webhook": { "url": "https://api.acme.com/wpp-v2", "secret": "novachavedepelomenos16chars" } }'
GET /v1/phones/:id/qrcode
Retorna o QR code Baileys atual (cacheado em Redis com TTL curto após o Worker gerar). Faça polling a cada 1-2s; quando o usuário escanear, o status vira CONNECTED e este endpoint passa a retornar 409.
Response 200
{
"qr": "2@LkPq...,QR data raw...",
"dataUrl": "data:image/png;base64,iVBORw0KGgo..."
}
dataUrl é PNG base64 pronto para <img src="...">.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 400 | QR_NOT_APPLICABLE | Phone não é BAILEYS |
| 404 | PHONE_NOT_FOUND | — |
| 409 | ALREADY_CONNECTED | Phone já conectou |
| 425 | QR_NOT_READY | QR ainda não emitido pelo Worker — tente em 1-2s |
| 425 | PAIRING_IN_PROGRESS | Phone em CONNECTING: o socket está subindo com credencial — pareamento recém-aceito ou canal pareado reconectando. Continue o polling: depois de um pareamento, vira CONNECTED em segundos; numa reconexão com a rede instável, pode levar minutos. Se passar de ~1 minuto, veja a nota abaixo |
O QR rota Baileys naturalmente refresha a cada ~20s. O endpoint devolve o último QR em cache — mas nunca um ref já consumido: assim que o celular aceita o pareamento, o QR é invalidado e o endpoint passa a responder 425 até a sessão abrir.
Isso importa para a sua tela: entre o "aceitei no celular" e o
CONNECTEDexistem alguns segundos. Exibir um QR nessa janela faz o usuário escanear um código morto, e o WhatsApp responde "verifique sua conexão e tente novamente" com o aparelho já vinculado. TratePAIRING_IN_PROGRESScomo "aguarde" — não como "escaneie de novo".A exceção é quando ele não passa.
CONNECTINGpor mais de um minuto tem duas causas que a API não distingue: credencial obsoleta — o WhatsApp recusa o login e, sem pedido de registro, nenhum QR será emitido; a saída éPOST /:id/logout— ou um canal saudável reconectando com a rede instável, em que o Worker recua até 5 minutos entre tentativas. Por isso ologouté decisão do usuário, com confirmação: ofereça-o depois de ~1 minuto, nunca o dispare sozinho. Ele desvincula o aparelho, e num canal saudável obriga a escanear de novo.
POST /v1/phones/:id/logout
Reinicia o pareamento: descarta a credencial do lado do gateway e, para
Baileys, abre uma sessão nova em seguida — mesmo id, mesmo webhook.
Responde 202 { status: 'logging_out' }; depois é só fazer polling em
GET /:id/qrcode até o QR novo sair.
Use quando o canal está preso: credencial obsoleta que não se apagou sozinha.
O wipe automático só acontece no 401 (loggedOut); em 403, 411, 440 e no
loop de 503 a identidade sobrevive, e com ela presente o Baileys manda nó de
LOGIN em vez de registro — nenhum QR é emitido e GET /qrcode responde 425
indefinidamente. Sem esta rota a única saída era DELETE /v1/phones/:id, que
troca o id e obriga a recriar o canal.
Não é preciso chamar POST /:id/reconnect depois — o próprio logout reabre.
E o reset é exclusivo: um reconnect (ou reconnect automático) que chegue durante
ele se junta ao reset em vez de subir a sessão com a credencial velha, e um
reconnect agendado antes dele é descartado.
No webhook, o reset aparece como channel:disconnected com
reason: "PAIRING_RESET", seguido de channel:qrcode quando a sessão nova emite
o QR.
Se a sessão antiga ainda responde, o gateway desvincula o aparelho no WhatsApp. Com a credencial já morta o desvínculo não chega ao WhatsApp, e a entrada antiga pode continuar em Aparelhos conectados no celular até o usuário removê-la.
Para WABA é encerramento de sessão (não há credencial Baileys a descartar), não reabre nada e não toca na Meta.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | PHONE_NOT_FOUND | — |
POST /v1/phones/:id/pairing-code
Alternativa ao QR para Baileys: gera um código de 8 caracteres que o usuário digita no celular em "Aparelhos conectados → Conectar com número".
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
phoneNumber | string | sim | E.164 (com ou sem +), 8-20 dígitos |
curl -X POST https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../pairing-code \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+5511988887777" }'
Response 202
{ "status": "pending", "phoneNumber": "5511988887777" }
A criação do código é assíncrona (Worker recebe o comando, cria a sessão Baileys com requestPairingCode, salva o resultado em Redis). Faça polling em GET /pairing-code.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 400 | PAIRING_NOT_APPLICABLE | Phone não é BAILEYS |
| 409 | ALREADY_CONNECTED | Phone já conectou |
GET /v1/phones/:id/pairing-code
Busca o pairing code gerado no comando anterior. Faça polling até 200.
Response 200
{ "code": "ABCD1234", "formatted": "ABCD-1234" }
Errors
| HTTP | Code | Quando |
|---|---|---|
| 425 | PAIRING_NOT_READY | Código ainda sendo gerado pelo Worker — tente em 1-2s |
O pairing code é único e descartável, e fica em cache por 2 minutos — depois disso este endpoint volta a responder
425. Quando o usuário digita o código no celular e a sessão abre, o Worker emitechannel:connected.
POST /v1/phones/:id/reconnect
Força o Worker a reabrir a sessão (útil quando o phone entrou em DISCONNECTED por instabilidade temporária). Para Baileys, tenta recuperar via auth state persistido. Para WABA, recarrega credenciais.
curl -X POST https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../reconnect \
-H "X-API-Key: ak_live_..."
Response 202
{ "status": "reconnecting" }
GET /v1/phones/:id/identities/resolve
Resolve uma identidade do contato. Aceita qualquer formato: canonicalKey, phone (5511999...), BSUID (BR.1234...), username (@maria), ou JID Baileys (5511999@s.whatsapp.net, 123@lid).
Query
| Param | Tipo | Obrigatório |
|---|---|---|
key | string | sim |
curl 'https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../identities/resolve?key=5511988887777' \
-H "X-API-Key: ak_live_..."
Response 200
{
"canonicalKey": "+5511988887777",
"pn": "5511988887777@s.whatsapp.net",
"lid": null,
"bsuid": null,
"username": null,
"parentBsuid": null,
"type": "pn"
}
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | IDENTITY_NOT_FOUND | Chave não encontrada nesse phone |
WABA Embedded Signup
Fluxo recomendado para WABA quando o cliente não tem credenciais Meta ainda. Usa o SDK JS da Meta no browser.
GET /v1/phones/waba/embedded-signup/config
Retorna config para o SDK Meta inicializar.
curl https://wpp.ogmma.com.br/v1/phones/waba/embedded-signup/config \
-H "X-API-Key: ak_live_..."
Response 200
{
"appId": "1234567890",
"configId": "9876543210",
"graphVersion": "v25.0",
"extras": {
"featureType": "whatsapp_business_app_onboarding",
"sessionInfoVersion": "3"
}
}
extras vai direto no FB.login({ config_id, response_type: 'code', override_default_response_type: true, extras: { setup: {}, ...extras } }).
featureType: "whatsapp_business_app_onboarding" é o que troca a tela de
seleção de WABA pela de conectar o WhatsApp Business existente
(coexistência). Sem ele, o fluxo é dedicado e o número sai do celular do dono.
Não hardcode esses valores no seu app: são contrato da Meta e mudam por decisão
dela.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 503 | META_APP_ID_MISSING | Gateway sem META_APP_ID configurado |
POST /v1/phones/waba/embedded-signup
Finaliza o signup após o usuário completar o fluxo no SDK Meta.
O callback devolve code + waba_id. Em coexistência não vem phone_number_id — veja o aviso abaixo.
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
code | string | sim | Authorization code retornado pelo SDK Meta |
phoneNumberId | string | não | ID do número. Em coexistência a Meta não devolve esse id ao popup — omita e o gateway descobre pela WABA |
wabaId | string | sim | ID da WABA |
label | string | não | Default: "WhatsApp Business" |
pin | string | não | PIN 6 dígitos para register. Só usado no fluxo dedicado — número de coexistência já chega registrado pela Meta e não tem PIN. Omitido, o gateway tenta "000000" (default pensado para desenvolvimento) |
permitirDedicado | boolean | não | Default false. Aceita conectar número que a Meta afirma não estar em coexistência — para reconectar um número que já era dedicado. Veja o aviso abaixo |
sync | object | não | { "contacts": bool, "history": bool } — importa os dados do celular. Só coexistência. Default: ambos false |
webhook | object | não | Config webhook (mesma do POST /phones) |
curl -X POST https://wpp.ogmma.com.br/v1/phones/waba/embedded-signup \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"code": "AQDxYzAbCd...",
"wabaId": "987654321098765",
"label": "WhatsApp Acme",
"sync": { "contacts": true, "history": true },
"webhook": { "url": "https://api.acme.com/wpp", "secret": "supersecret-pelomenos-16chars" }
}'
Sem phoneNumberId (coexistência) e sem pin (só o fluxo dedicado usa). O
webhook vai junto porque o sync só é pedido à Meta se houver para onde
entregar os dados.
Response 201 — phone criado, com um bloco registration aditivo:
{
"id": "5f7b2e1c-...",
"status": "CONNECTED",
"registration": { "ok": true, "coexistence": true, "reconnected": false, "platformType": "CLOUD_API", "reason": null },
"sync": {
"contacts": { "ok": true, "flagPersisted": true, "requestId": "..." },
"history": { "ok": true, "flagPersisted": true, "requestId": "..." }
}
}
O gateway executa:
- Trocar
codepor system user token (server-side, comMETA_APP_SECRET). - Sem
phoneNumberIdno corpo, descobrir o número pela WABA. - Ler o estado do número (
platform_type+is_on_biz_app) — é essa leitura que separa coexistência de dedicado. Ela vem antes de qualquer escrita na Meta: pedido recusado não deixa o app inscrito na WABA nem o número registrado. subscribeAppToWabapara receber webhooks.- Registrar o número só se ele ainda não fala pela Cloud API — e só em
canal novo. Número de coexistência a Meta já registrou sozinha quando o dono
confirmou no celular; chamar
/registernele é errado. - Persistir phone (com a metadata da leitura mais recente do número) + disparar
channel:connectno Worker. - Se
syncfoi pedido e o número é coexistente, disparar a SMB App Data API.
registration.coexistence: false num número que você esperava coexistente
significa que o WhatsApp Business do celular não continua ativo — e que não
haverá eco nem histórico. reason traz o texto pronto pra UI.
Coexistência devolve só o
waba_id. ComsessionInfoVersion: 3, o evento de sucesso do popup éFINISH_WHATSAPP_BUSINESS_APP_ONBOARDINGe odatacarrega apenaswaba_id— não háphone_number_id. Seu app precisa escutar esse nome de evento (não sóFINISH) e pode chamar este endpoint semphoneNumberId.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 503 | META_APP_NOT_CONFIGURED | Sem META_APP_ID/META_APP_SECRET no servidor |
| 409 | WABA_SEM_NUMERO | A WABA não tem número — o Embedded Signup não foi concluído |
| 409 | PHONE_NUMBER_ID_INDISPONIVEL | A Meta não devolveu o id do único número da WABA. Tente de novo |
| 409 | PHONE_NUMBER_ID_AMBIGUO | Mais de um número da WABA está em coexistência. Informe phoneNumberId — os candidatos vêm em details.candidatos |
| 409 | COEXISTENCIA_INDETERMINADA | A Meta ainda não confirmou o número (propagação): ou não informou quais números da WABA estão em coexistência (details.candidatos traz os ids — dá para informar phoneNumberId), ou ainda não reporta o número como ativo na Cloud API. Espere um minuto e repita. permitirDedicado não contorna este erro |
| 409 | NENHUM_NUMERO_COEXISTENTE | Nenhum número da WABA está em coexistência. Refaça o Embedded Signup escolhendo o número do celular — ou, se o número já era dedicado, informe phoneNumberId (os ids vêm em details.candidatos) com permitirDedicado: true |
| 409 | PHONE_JA_CONECTADO | O número já está conectado em outra account |
| 409 | CONEXAO_NOVA_EXIGE_COEXISTENCIA | Conexão nova cujo número a Meta informou não estar em coexistência. Refaça escolhendo o número do WhatsApp Business do celular — ou, se o número já era dedicado, repita com permitirDedicado: true |
Neste deploy, toda conexão nova nasce em coexistência (ADR 0003). A regra vale enquanto
META_ES_FEATURE_TYPEservir coexistência — em deploy configurado para o fluxo dedicado, ela não se aplica. Um número novo que a Meta informe não estar em coexistência é recusado comCONEXAO_NOVA_EXIGE_COEXISTENCIA. Só ofalseexplícito recusa: campo ausente é propagação do Graph, não recusa, e passa.Número que já era dedicado: mande
permitirDedicado: true. É o caso de um canal WABA dedicado apagado sem?deregister=true— o número segue registrado na Cloud API, a Meta respondeis_on_biz_app: falsee, sem o opt-in, ele seria recusado para sempre. O opt-in vale só para essa recusa: um número que ainda não opera na Cloud API continua recebendoCOEXISTENCIA_INDETERMINADA, porque seguir registraria um dedicado novo. O número conectado assim não volta ao WhatsApp do celular.Reconectar canal que já existe continua funcionando, inclusive dedicado antigo — a recusa acima vale só para canal novo. Nesse caso a resposta é 200 com
registration.reconnected: true, e o canal é reavaliado: o gateway relê a Meta, atualiza o modo de conexão (só promove a coexistência, nunca rebaixa) e aplica o blocowebhookdo corpo (inteiro, nunca campo a campo). Osyncsó roda se o número for coexistente — em canal dedicado ele é ignorado em silêncio, então confiraregistration.coexistenceantes de esperar dados. É o caminho normal depois de um offboard.
POST /v1/phones/:id/waba/smb-sync
Importa contatos e/ou histórico do WhatsApp Business do celular. Só para número coexistente.
Body
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
contacts | boolean | false | Importa a agenda do celular |
history | boolean | false | Importa até 180 dias de conversa |
force | boolean | false | Ignora a marca local de "já sincronizado" e deixa a Meta decidir |
Pelo menos um entre contacts e history precisa ser true.
curl -X POST https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../waba/smb-sync \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "contacts": true, "history": true }'
Response 202
{
"contacts": { "ok": true, "flagPersisted": true, "requestId": "..." },
"history": { "ok": false, "error": "ALREADY_SYNCED" }
}
| Campo | Descrição |
|---|---|
ok | A Meta aceitou o pedido |
requestId | Guarde — é o que o suporte da Meta pede |
flagPersisted | false = a Meta aceitou mas o gateway não conseguiu gravar a marca. A cota foi gasta; uma nova tentativa será recusada pela Meta |
error | ALREADY_SYNCED (marca local — use force para insistir), SEM_DESTINO_DE_WEBHOOK (o phone não tem webhook que entregue — nada foi pedido à Meta), WEBHOOK_EVENTS_MISSING (o tipo não está assinado; missingEvents diz o que falta) ou a mensagem da Meta |
Saída da porta de mão única: se a Meta aceitou mas o webhook nunca chegou (o dono demorou, o processo reiniciou),
force: truerefaz o pedido. Aí quem recusa é a Meta, com o erro real dela, em vez da nossa marca local — que de outro modo só sairia comUPDATEno banco.
O 202 diz que a Meta aceitou o pedido — não que o dono autorizou
compartilhar. Os dados chegam depois, nos webhooks contacts:sync e
history:messages; a recusa chega como history:failed.
Duas restrições da Meta, as duas irreversíveis:
- uma vez por número e por tipo (repetido vira
ALREADY_SYNCED);- dentro de 24h do onboarding.
Passado o prazo ou gasta a chance, a única saída é o dono desconectar no celular e refazer o Embedded Signup inteiro.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 400 | SMB_SYNC_EMPTY | Nem contacts nem history pedidos |
| 404 | PHONE_NOT_FOUND | Phone não é WABA ou está sem credenciais |
| 409 | NOT_COEXISTENT | Número dedicado — não há dados de app de celular |
| 409 | WEBHOOK_EVENTS_MISSING | Nenhum dos tipos pedidos está assinado: o webhook do phone tem lista explícita de events sem os de importação. details.missingEvents diz quais faltam |
Assine os eventos ANTES de pedir o sync. Se o phone tem
webhook.eventspreenchido, ele precisa incluirhistory:messages,history:failedecontacts:sync— lista vazia recebe tudo. O gateway recusa o pedido em vez de queimar a chance única da Meta em dados que não teriam para onde ir. Ajuste comPATCH /v1/phones/:id.
Instagram OAuth
GET /v1/phones/instagram/oauth/config
Retorna config para iniciar o fluxo OAuth Instagram.
Response 200
{
"appId": "...",
"redirectUri": "https://app.acme.com/oauth/instagram/callback",
"scope": "instagram_business_basic,instagram_business_manage_messages",
"authorizationUrl": "https://www.instagram.com/oauth/authorize?client_id=...&redirect_uri=..."
}
POST /v1/phones/instagram
Finaliza o OAuth com o code recebido no redirect.
Body
| Campo | Tipo | Obrigatório |
|---|---|---|
code | string | sim |
label | string | não |
webhook | object | não |
curl -X POST https://wpp.ogmma.com.br/v1/phones/instagram \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "code": "AQDxYz...", "label": "Instagram Acme" }'
Response 201 — phone com type: INSTAGRAM, status: CONNECTED, token long-lived (60d) armazenado criptografado.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 503 | IG_NOT_CONFIGURED | Servidor sem credenciais Instagram |
WABA Business Profile
GET /v1/phones/:id/business-profile
Retorna o perfil público do número WABA (about, websites, vertical, etc) direto do Meta.
curl https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../business-profile \
-H "X-API-Key: ak_live_..."
PATCH /v1/phones/:id/business-profile
Atualiza o perfil.
Body (todos opcionais)
| Campo | Tipo | Limite |
|---|---|---|
about | string | 139 chars |
address | string | 256 chars |
description | string | 512 chars |
email | — | |
websites | URL[] | até 2 |
vertical | enum | AUTO, BEAUTY, RETAIL, RESTAURANT, etc. |
Response 204
WABA Phone Number lifecycle
Endpoints administrativos do número (alternativa ao Embedded Signup quando o cliente quer controle granular).
POST /v1/phones/:id/request-code
Solicita código de verificação SMS ou voz.
Body
| Campo | Tipo | Default |
|---|---|---|
method | SMS | VOICE | SMS |
locale | string | pt_BR |
Response 204
POST /v1/phones/:id/verify-code
Confirma o código recebido por SMS/voz.
Body: { "code": "123456" } → Response 204
POST /v1/phones/:id/register
Registra o número na WABA (passo final). PIN obrigatório.
Body: { "pin": "123456" } → Response 204
POST /v1/phones/:id/deregister
Desregistra o número (libera para outra WABA).
Response 204
POST /v1/phones/:id/request-name-change
Solicita mudança de display name (precisa aprovação Meta).
Body: { "name": "Acme Atendimento" } (3-60 chars) → Response 200 { "id": "..." } — o id do pedido na Meta.
POST /v1/phones/:id/two-step-pin
Define o PIN de 6 dígitos (2-step verification).
Body: { "pin": "123456" } → Response 204
POST /v1/phones/:id/rotate-token
Substitui o access token armazenado (sem precisar re-onboardar). Útil quando o System User Token é rotacionado no Business Manager.
Body: { "accessToken": "EAAxxx..." } → Response 204
Validação: o gateway chama getPhoneNumberInfo com o novo token antes de salvar. Token recusado pela Meta responde 400 INVALID_TOKEN, e o antigo continua valendo.
WABA Block/Unblock
GET /v1/phones/:id/blocked
Lista usuários bloqueados pelo número (anti-spam).
Response 200
{ "users": [{ "input": "5511988887777", "wa_id": "5511988887777", ... }] }
POST /v1/phones/:id/block
Body: { "users": ["5511988887777", ...] } (1-100 itens) → Response 204
POST /v1/phones/:id/unblock
Mesmo formato. Response 204
WhatsApp Business Account (WABA-level)
Endpoints para inspeção da WABA inteira (não do número individual).
GET /v1/phones/:id/waba
Retorna info da WABA.
GET /v1/phones/:id/waba/phones
Lista todos os números registrados naquela WABA.
GET /v1/phones/:id/waba/subscribed-apps
Lista apps Meta com webhook subscrito.
POST /v1/phones/:id/waba/subscribed-apps
Re-subscreve o app gateway aos webhooks.
Body (opcional)
| Campo | Tipo |
|---|---|
overrideCallbackUri | URL |
verifyToken | string |
Response 204
DELETE /v1/phones/:id/waba/subscribed-apps
Remove subscription. Response 204