Pular para o conteúdo principal

Calls (WABA Voice)

Suporte a chamadas de voz via Meta WhatsApp Calling API. WABA-only.

Base path: /v1/calls

O gateway é stateless de áudio. Cliente final é responsável pelo WebRTC (browser ou app). O gateway só faz signaling com a Meta (offer/answer, accept/reject/hangup) e persiste metadata da call.


Arquitetura

┌──────────────┐ WebRTC P2P ┌──────────────┐
│ Browser │◄────────────►│ WhatsApp │
│ (seu cliente)│ │ do contato │
└──────┬───────┘ └──────────────┘
│ HTTPS REST (SDP)

┌──────────────┐
│ wpp-gateway │ signaling ┌──────────────┐
│ /v1/calls │◄───────────►│ Meta WABA │
└──────────────┘ │ Cloud API │
└──────────────┘

Gateway responsabilidades:

  • Persiste call lifecycle (waba_calls).
  • Chama Meta para make, accept, reject, hangup.
  • Repassa eventos da Meta via webhook (call:status futuro — ver meta-webhook.ts).
  • Recebe upload de recording (mixagem local+remote feita no browser, MediaRecorder → octet-stream).
  • Opcional: transcreve recording via Whisper.

Não faz: WebRTC, transcode, mixagem.


Lifecycle

INBOUND:
Webhook Meta → call:offer → seu app


POST /v1/calls/:id/accept (SDP answer)


ACCEPTED → IN_PROGRESS


POST /v1/calls/:id/hangup


COMPLETED

OUTBOUND:
POST /v1/calls (SDP offer)


RINGING → ACCEPTED → IN_PROGRESS → COMPLETED

Status: RINGING, ACCEPTED, IN_PROGRESS, COMPLETED, REJECTED, MISSED, FAILED.


POST /v1/calls

Inicia uma call outbound. Cliente já gerou o SDP offer via WebRTC.

Body

CampoTipoObrigatórioLimite
phoneIdUUIDsimPhone WABA
tostringsim5-60 chars (E.164 ou BSUID)
sdpOfferstringsim≥20 chars (SDP completo gerado por RTCPeerConnection.createOffer())
curl -X POST https://wpp.ogmma.com.br/v1/calls \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"phoneId": "5f7b2e1c-...",
"to": "5511988887777",
"sdpOffer": "v=0\r\no=- 4611...\r\ns=-\r\nt=0 0\r\n..."
}'

Response 201

{
"id": "call-1234-...",
"accountId": "...",
"phoneId": "5f7b2e1c-...",
"metaCallId": "wamid.HBgL...",
"direction": "OUTBOUND",
"status": "RINGING",
"peer": "5511988887777",
"createdAt": "..."
}

Pré-requisito: o destinatário precisa ter dado call permission ao seu phone. Sem isso, Meta retorna call_permission_not_granted. Veja POST /v1/calls/permission-request abaixo.


GET /v1/calls

Lista calls.

Query

ParamDefaultMax
phoneIdfiltro
statusfiltro
limit50100

GET /v1/calls/:id

Detalhe. :id aceita UUID local ou metaCallId.

curl https://wpp.ogmma.com.br/v1/calls/call-1234-... \
-H "X-API-Key: ak_live_..."

Response 200 — objeto WabaCall.

Errors

HTTPCodeQuando
404CALL_NOT_FOUND

POST /v1/calls/:id/accept

Aceita uma call inbound. Cliente já gerou o SDP answer.

Body

CampoTipoObrigatórioLimite
sdpAnswerstringsim≥20 chars
curl -X POST https://wpp.ogmma.com.br/v1/calls/call-1234-.../accept \
-H "X-API-Key: ak_live_..." \
-d '{ "sdpAnswer": "v=0\r\no=- ..." }'

POST /v1/calls/:id/reject

Rejeita inbound (sem atender).

Body (opcional)

CampoTipo
reasonstring ≤200 chars

POST /v1/calls/:id/hangup

Encerra call ativa.

Body (opcional)

CampoTipo
reasonstring ≤200 chars

Call Permission Request

Pré-requisito Meta para business-initiated calls: o destinatário precisa ter aprovado calls do seu phone nos últimos 7 dias.

POST /v1/calls/permission-request

Envia uma mensagem solicitando permissão. Aparece como UI nativa Meta no app do destinatário.

Body

CampoTipo
phoneIdUUID
tostring
curl -X POST https://wpp.ogmma.com.br/v1/calls/permission-request \
-H "X-API-Key: ak_live_..." \
-d '{ "phoneId": "5f7b2e1c-...", "to": "5511988887777" }'

GET /v1/calls/permissions

Verifica se um contato já deu permissão.

Query: phoneId=<uuid>&to=<phone>


Recording

O gateway aceita upload do arquivo gravado no browser via MediaRecorder. Audio é armazenado em S3 (recordings/<accountId>/<callId>.<ext>). Lifecycle: 90 dias.

POST /v1/calls/:id/recording

Upload do binário cru. Body: Buffer (audio/webm, mp4, mp3, ogg). Limite: 50MB.

curl -X POST https://wpp.ogmma.com.br/v1/calls/call-1234-.../recording \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: audio/webm" \
--data-binary @recording.webm

Response 201

{
"callId": "call-1234-...",
"s3Key": "recordings/<accountId>/call-1234-....webm",
"sizeBytes": 234567,
"contentType": "audio/webm"
}

Errors

HTTPCodeQuando
400RECORDING_EMPTYBody não é Buffer ou tamanho 0
404CALL_NOT_FOUND

GET /v1/calls/:id/recording

Redirect para signed URL (TTL 10min).

Response 302Location: https://s3...

Errors

HTTPCodeQuando
404RECORDING_NOT_FOUNDRecording ainda não foi upado

Transcrição da gravação

Mesmo fluxo de Media → Transcribe.

POST /v1/calls/:id/transcribe

curl -X POST https://wpp.ogmma.com.br/v1/calls/call-1234-.../transcribe \
-H "X-API-Key: ak_live_..."

Response 202 (primeira chamada): { "status": "PENDING", "callId": "..." }.

Response 200 (cache hit): { "status": "COMPLETED", "text": "...", "language": "pt", "transcribedAt": "...", "cached": true }.

Errors

HTTPCodeQuando
404RECORDING_NOT_FOUNDSem recording uploaded ainda

GET /v1/calls/:id/transcription

Estado atual.

{
"status": "COMPLETED",
"text": "Olá, gostaria de saber...",
"language": "pt",
"transcribedAt": "...",
"error": null
}

Errors

HTTPCodeQuando
404TRANSCRIPTION_NOT_REQUESTEDVocê não chamou POST /transcribe ainda

Boas práticas

  1. Sempre solicite permission antes de tentar outbound — economiza falhas.
  2. Upload recording imediatamente no hangup — browsers podem perder o MediaRecorder ao trocar de tab.
  3. Mostre call status na UI via SSE (channel:status ou eventos específicos call:*) — mudanças são rápidas (RINGING ~5s timeout).