Guia — Bot WhatsApp via API

Integre seu bot existente

Para merchants que já têm WhatsApp + bot próprios. A Bumcash entrega os dados (saldo do cliente, rede de aceitação, configuração do merchant) via API REST; seu bot continua falando pelo seu canal. Sem conexão WhatsApp pelo lado da Bumcash.

Se você quer que a Bumcash cuide do canal, veja a integração WhatsApp gerenciada (caminho separado).

Modelo mental — leia antes dos curls

A integração tem dois identificadores assimétricos:

DimensãoCPFTelefone
ValidaçãoValidado na ativaçãoSem validação automática
ImutabilidadePermanenteMuda; operadoras reciclam após ~90d
PapelIdentifica o clienteCanal de comunicação

O bot recebe telefone do WhatsApp. Quando consulta a API por telefone, a resposta é uma de três:

EstadoSignificadoAção do bot
verifiedCaller tem vínculo + telefone atestado nos últimos 12 meses.Use os dados retornados normalmente.
staleVínculo existe mas o telefone foi atestado há mais de 12 meses.Peça CPF e chame POST /customers/{cpf}/confirm-phone.
indistinguishableTelefone não existe na sua base OU pertence a outro merchant (resposta bit-a-bit idêntica).Peça CPF e use lookup por CPF / register.

Por CPF a resposta é mais informativa (três estados claros: linked, exists_not_linked, not_exists) porque CPF não é enumerável da mesma forma.

Pré-requisitos

  • CNPJ aprovado pela equipe Bumcash (status approved no portal).
  • Termos de Uso aceitos no portal.
  • Pelo menos uma location ativa.
  • Chave Merchant (bmc_test_mch_… para sandbox, bmc_live_mch_… para produção) com customers:query e customers:register — ou chave Partner (ptr_) se você integra os lojistas; veja a seção Chaves Partner abaixo.

Demais permissões (payments:create, recurring:bill_payment, etc.) conforme o bot evoluir para fechar transações — não são necessárias para consultas.

URL base

txt
https://api-v2.bumcash.com.br/v1

Passo 1

Descobrir sua própria configuração

Antes do primeiro inbound, descubra as taxas do seu merchant para que o bot possa responder "se você comprar R$ 100, ganha R$ X em bumcash" sem chutar.

GET/v1/locations/meperm: mch_ only
curlcurl
curl https://api-v2.bumcash.com.br/v1/locations/me \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx"
jsonresposta — 200 OK
{
  "merchant": {
    "org_id": "org_xyz",
    "name": "Pizzaria do John",
    "company_type": "retail",
    "approved": true
  },
  "locations": [
    {
      "id": "loc_f1b2",
      "name": "Matriz",
      "pos_cashback_rate": 30,
      "coin_acceptance_rate": 30,
      "recurrency_conversion_rate": 100,
      "credit_expiration_days": 29,
      "billing_cycle": "monthly",
      "address": { "city": "São Paulo", "state": "SP" }
    }
  ]
}

Cache esse retorno no seu bot. As taxas mudam raramente — atualizar uma vez por dia é suficiente.

Passo 2

Identificar o cliente do inbound

Cliente mandou "saldo" pelo WhatsApp. O bot sabe o telefone do remetente. Antes de personalizar a resposta, consulte:

GET/v1/customers/lookupperm: customers:query
curlcurl
curl 'https://api-v2.bumcash.com.br/v1/customers/lookup?phone=%2B5511987654321' \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx"

Rate limit: 100 req/min por chave (bucket customers.lookup). 429 vem com header Retry-After.

2.1 — Cliente vinculado e telefone atualizado

json
{
  "by": "phone",
  "exists": true,
  "linked_to_caller": true,
  "phone_confidence": "verified",
  "customer": {
    "id": "cust_a1b2c3",
    "status": "active",
    "registered_at_caller": "2026-04-10T14:00:00.000Z",
    "activated_at_caller": "2026-05-02T09:12:00.000Z",
    "external_reference_id": "john_subscription_42",
    "phone": "+5511987654321"
  }
}

Bot pode prosseguir — consultar saldo, exibir histórico, etc.

2.2 — Vinculado mas telefone está stale (>12 meses)

json
{
  "by": "phone",
  "exists": true,
  "linked_to_caller": true,
  "phone_confidence": "stale",
  "requires_phone_refresh": true,
  "customer": { "id": "cust_a1b2c3", "status": "active" }
}

Bot deve pedir CPF ao cliente, depois chamar POST /customers/{cpf}/confirm-phone (Passo 4).

2.3 — Não distinguível (anti-enumeration)

json
{
  "by": "phone",
  "exists": null,
  "linked_to_caller": false,
  "requires_cpf_confirmation": true
}

Pode ser:

  • • Telefone novo (lead potencial)
  • • Telefone de cliente de outro merchant
  • • Telefone reciclado pela operadora

Passo 3

Consultar o saldo

Com phone_confidence: "verified" (ou após confirmar via CPF):

GET/v1/customers/:phone/balanceperm: customers:query
curlcurl
curl 'https://api-v2.bumcash.com.br/v1/customers/+5511987654321/balance' \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx"
jsonresposta — 200 OK
{
  "customer_id": "cust_a1b2c3",
  "balance": 5045,
  "balance_brl": "50,45",
  "tier": { "name": "Ouro", "consecutive_payments": 8 },
  "expiration": {
    "expiring_within_7_days": 0,
    "expiring_within_30_days": 5045,
    "next_expiration_at": "2026-06-19T00:00:00.000Z",
    "next_expiration_amount": 5045
  }
}

Use balance (centavos) ou balance_brl (string formatada) na resposta do bot. Se expiring_within_7_days > 0, considere alertar o cliente que o saldo está prestes a expirar.

Rate limit: 60 req/min por chave (bucket customers.balance).

HTTPerror.codeCausaO que fazer
403phone_verification_staleVínculo existe mas o telefone está stale.Use confirm-phone (Passo 4).
404customer_not_linked_by_phoneTelefone não tem vínculo OU não existe (resposta indistinguível).Peça o CPF.
404customer_not_linkedLookup por CPF resultou em "existe mas não é seu cliente".Use customers/register antes.

Passo 4

Renovar atestado do telefone após coletar CPF

Quando o lookup retornou stale ou indistinguishable, o bot pediu o CPF ao cliente. Para reatar o vínculo e desbloquear consultas por telefone:

POST/v1/customers/:cpf/confirm-phoneperm: customers:register
curlcurl
curl -X POST 'https://api-v2.bumcash.com.br/v1/customers/12345678909/confirm-phone' \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+5511987654321",
    "idempotency_key": "renovar_john_2026-05-21"
  }'
jsonresposta — 200 OK
{
  "customer_id": "cust_a1b2c3",
  "phone": "+5511987654321",
  "phone_verified_at": "2026-05-21T15:30:00.000Z"
}

Rate limit: 60 req/min por chave (bucket customers.confirm_phone).

HTTPerror.codeCausaO que fazer
404customer_not_foundCPF não existe OU existe mas você não tem vínculo (mesma proteção anti-enumeration).Confira o CPF com o cliente.
409phone_conflictO telefone já está atrelado a outro cliente seu.Confirme o número com o cliente.
400invalid_cpfCPF não passa na validação de dígito verificador.Reconfirme com o cliente.

Passo 5

"Onde posso usar?" — descoberta da rede

Cliente perguntou pela rede de aceitação. Use coordenadas (se o bot tiver) ou apenas cidade:

GET/v1/network/locationsperm: —
curlcurl — modo geo
curl 'https://api-v2.bumcash.com.br/v1/network/locations?near=-23.5505,-46.6333&radius_km=5&limit=10' \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx"
curlcurl — modo cidade
curl 'https://api-v2.bumcash.com.br/v1/network/locations?city=S%C3%A3o%20Paulo&state=SP&limit=10' \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx"
jsonresposta — 200 OK (geo, ordenado por distância)
{
  "data": [
    {
      "id": "loc_f1b2",
      "name": "Pizzaria do John",
      "category": "alimentacao",
      "address": {
        "street": "Rua das Flores",
        "number": "123",
        "neighborhood": "Vila Mariana",
        "city": "São Paulo",
        "state": "SP",
        "zipCode": "04101-000"
      },
      "phone": "+551155554444",
      "coin_acceptance_rate": 30,
      "operating_hours": { "mon": { "open": "11:00", "close": "23:00" } },
      "logo_url": "https://cdn.bumcash.com.br/logos/location/loc_f1b2/v3.webp",
      "distance_km": 1.4
    }
  ],
  "has_more": true,
  "cursor": null
}

Cada item traz logo_url — o logo da loja, com fallback para o logo do lojista (null quando nenhum foi enviado). Use direto no cabeçalho do template ou no card da lista.

Para montar templates de WhatsApp (lista interativa, carrossel), passe compact=true: os mesmos dados, mas com address e operating_hours achatados em strings (address, hours) — cada campo mapeia direto numa variável de template:

curlcurl — versão compacta p/ template
curl 'https://api-v2.bumcash.com.br/v1/network/locations?near=-23.5505,-46.6333&radius_km=5&limit=10&compact=true' \
  -H "Authorization: Bearer bmc_test_mch_xxxxxxxxxxxxxxxxxx"
jsonresposta — 200 OK (compact=true)
{
  "data": [
    {
      "id": "loc_f1b2",
      "name": "Pizzaria do John",
      "category": "alimentacao",
      "address": "Rua das Flores, 123, Vila Mariana, São Paulo/SP",
      "phone": "+551155554444",
      "hours": "11:00-23:00",
      "coin_acceptance_rate": 30,
      "logo_url": "https://cdn.bumcash.com.br/logos/location/loc_f1b2/v3.webp",
      "distance_km": 1.4
    }
  ],
  "has_more": true,
  "cursor": null
}

hours é o horário de hoje (fuso de São Paulo) — null quando a loja não abre hoje. A agenda completa por dia continua na versão não-compacta (operating_hours).

No modo geo, paginação se faz aumentando o radius_km (máx 50); não há cursor. No modo lista (sem near), use cursor + has_more normalmente. Recurrency é excluída por default (sem balcão).

Chaves Partner — bot para os clientes dos seus lojistas

Todo este guia também funciona com chave Partner (bmc_test_ptr_… / bmc_live_ptr_…): um integrador pode operar o bot em nome dos lojistas que atende. Os quatro endpoints de cliente (lookup, balance, confirm-phone, register) aceitam ptr_ com as mesmas permissões (customers:query / customers:register, já inclusas no conjunto padrão da chave Partner), os mesmos três estados e a mesma anti-enumeration. Três diferenças:

  • • Vínculo: sua chave enxerga os clientes registrados/ativados nas locations que você integra (vinculadas ao seu org_id de integrador), ou com transação nessas locations nos últimos 12 meses. Clientes de lojistas fora da sua carteira aparecem como not linked / indistinguishable, igual para qualquer outro caller.
  • • Register: chaves Partner devem identificar o lojista alvo com cnpj OU location_id (exatamente um) — chaves Merchant não enviam nenhum dos dois. A atribuição do cliente vai para o lojista (não para o integrador).
  • • Configuração: em vez de GET /locations/me (que responde 403 wrong_scope para ptr_), use GET /v1/locations/lookup?cnpj=… para as taxas de uma location específica.
curlcurl — mesmos endpoints, chave ptr_
curl 'https://api-v2.bumcash.com.br/v1/customers/lookup?phone=%2B5511987654321' \
  -H "Authorization: Bearer bmc_test_ptr_xxxxxxxxxxxxxxxxxx"

curl 'https://api-v2.bumcash.com.br/v1/locations/lookup?cnpj=11222333000181' \
  -H "Authorization: Bearer bmc_test_ptr_xxxxxxxxxxxxxxxxxx"

Fluxo recomendado de bot

inbound ──▶ GET /customers/lookup?phone=...
             │
             ├─▶ verified ─────────▶ atendimento personalizado (saldo, histórico, …)
             │
             ├─▶ stale ────────────▶ "Para sua segurança, me passa seu CPF?"
             │                       └─▶ POST /customers/{cpf}/confirm-phone
             │
             └─▶ indistinguishable ▶ "Olá! Você é cliente Bumcash? Me passa seu CPF."
                                     ├─ já é cliente seu: POST /customers/{cpf}/confirm-phone
                                     └─ ainda não: POST /customers/register

Armazene (phone, cpf, customer_id) no banco do bot após cada confirmação. Próximas conversas pelo mesmo telefone vão direto ao caminho verified.

Erros que você vai encontrar

HTTPerror.codeCausaO que fazer
400invalid_identifierLookup sem cpf nem phone, ou com ambos.Envie exatamente um.
400invalid_cpfCPF não passa no dígito verificador.Reconfirme com o cliente.
403phone_verification_staleTelefone vinculado mas atestado há mais de 12 meses.Peça CPF, chame confirm-phone.
403wrong_scopeChave Partner em /locations/me, ou chave Merchant em /locations/lookup.Merchant: use /locations/me. Partner: use /locations/lookup?cnpj=.
404customer_not_linked_by_phoneTelefone não tem vínculo OU não existe (anti-enumeration).Peça CPF.
409phone_conflictTelefone já atrelado a outro cliente seu.Reconfirme com o cliente.
429rate_limit_exceededExcedeu o bucket por minuto.Respeite o header Retry-After.

Veja a referência completa em códigos de erro.

Privacidade & compliance

  • Nunca logue CPF ou telefone em claro do seu lado se puder evitar. A Bumcash não os retorna em audit logs (apenas hashes sha256), e seu lado deveria seguir a mesma política.
  • Cross-merchant: você nunca verá dados que indiquem que um cliente tem relação com outro merchant. Se uma consulta por CPF retorna linked_to_caller: false, os campos sensíveis (id, phone, external_reference_id) vêm omitidos.
  • Resposta indistinguível: respostas indistinguishable_by_phone e customer_not_linked_by_phone são deliberadamente idênticas para "número de outro merchant" e "número desconhecido". Aceite isso como propriedade do sistema — não tente diferenciar.
  • LGPD (Lei 13.709): tratamento de dados de clientes está coberto pela execução de contrato + interesse legítimo (programa de fidelidade). Mantenha um propósito declarado para coletar telefone e respeite pedidos de exclusão pelo seu canal.

Próximos passos

  • Pagamentos / cashback: combine este guia com a trilha do seu modelo de negócio — veja Recorrência, Varejo PDV ou Híbrido.
  • Eventos em tempo real: webhooks de customer.activated, cashback.granted, etc. Hoje exigem chave Partner — merchant-direct webhooks estão no roadmap.
  • Monitoramento: a Bumcash mantém audit log estruturado de toda chamada bot-via-API com hash sha256 do identificador. Sinais de enumeração (>80% not_linked em 1h) disparam alerta — ajuste a UX do bot para não bater 100/min cegamente.