Desenvolvedores
Referência da API
Um endpoint para enviar, um para consultar o status, webhooks assinados com HMAC para os recibos. As chaves são emitidas no momento em que a conta é criada — não há nada para aprovar e ninguém para avisar por e-mail.
Primeiros passos
Três coisas ficam entre uma conta nova e uma mensagem entregue: um token, algum crédito e uma chave. Nenhuma delas envolve um humano.
- Crie uma conta — seu navegador gera o token, e esse token é a conta.
- Deposite. Mínimo de $40, em qualquer uma das moedas em a página de pagamentos.
- Crie uma chave de API no painel. Ela é exibida uma única vez; armazenamos apenas seu hash, exatamente como o token.
Uma chave com saldo zerado não pode enviar
A criação de chaves é deliberadamente bloqueada até que a conta tenha saldo. Uma credencial não utilizada por aí é um risco sem nenhuma vantagem, então o painel não gera uma até que haja algo para gastar.
Autenticação
Token de portador em cada solicitação. A chave identifica a conta; não há segundo fator, nenhuma assinatura na solicitação e nenhuma lista de IPs permitidos — a chave é toda a credencial, então trate-a como tal.
Authorization: Bearer ws_live_9f2c…
| Transporte | Somente HTTPS. Uma requisição em HTTP simples é recusada, não redirecionada — um redirecionamento já teria vazado a chave. |
|---|---|
| Escopo | Uma chave gasta o saldo de uma conta. Crie uma chave por sistema que envia, para que um vazamento seja revogado de forma restrita. |
| Revogação | Imediato. Uma chave revogada retorna 401 na próxima requisição; lotes em andamento já aceitos ainda são concluídos. |
| Recuperação | Nenhuma. Guardamos um hash. Uma chave perdida é substituída, nunca recuperada. |
Enviar mensagens
POST/v1/messages
Uma chamada, um ou vários destinatários, destinos combinados. Cada destinatário é precificado na taxa do seu próprio destino e cobrado por segmento. A chamada é aceita ou recusada por completo; ela nunca cobra você parcialmente.
Corpo da requisição
| Campo | Tipo | Notas |
|---|---|---|
to | string[] | Obrigatório. E.164, até 5.000 por chamada. Números que não correspondem a um destino precificado são rejeitados antes da cobrança. |
text | string | Obrigatório. UTF-8. A contagem de segmentos segue o GSM 03.38 — veja segmentos. |
from | string | ID de remetente alfanumérico, com até 11 caracteres. Carregado onde a rota permite; veja IDs de remetente. |
vars | object[] | Valores de mesclagem opcionais, um objeto por destinatário, substituídos nos marcadores {{name}} antes de os segmentos serem contados. |
callback_url | string | Substituição opcional do webhook da conta, por lote. |
reference | string | Opcional. Retornado em cada recibo para que você possa vincular aos seus próprios registros. |
Exemplo
# one call, many recipients, mixed destinations
curl -X POST https://api.worldsms.io/v1/messages \
-H "Authorization: Bearer $WORLDSMS_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "ACME",
"to": ["+447700900123", "+33612345678"],
"text": "Your code is 480913"
}'
{
"batch_id": "b_7Kq2xR9wLm",
"accepted": 2,
"rejected": 0,
"segments": 1,
"cost_usd": 0.0353
}
0,0137 $ para o Reino Unido mais 0,0216 $ para a França — a tabela de preços aplicada por destino, não uma média combinada.
Agrupamento em lote
Um lote é aceito imediatamente e escoa a 200 mensagens por minuto por conta, então 50.000 destinatários levam cerca de 4 h 10 min. Tráfego sensível ao tempo não deve ficar na fila atrás de um envio de marketing: use uma chave separada para ele.
Status da mensagem
GET/v1/messages/{batch_id}
O polling é suportado e tem limite de taxa; os webhooks são o caminho pretendido. A resposta consolida o lote e lista o estado por destinatário.
{
"batch_id": "b_7Kq2xR9wLm",
"status": "done",
"sending": 0,
"delivered": 1,
"undelivered": 1,
"refund_usd": 0.0216
}
refund_usd é liquidado uma única vez, quando o lote termina. Já está de volta no seu saldo quando você consegue lê-lo.
Saldo
GET/v1/balance
Verifique antes de um envio grande. Os saldos são mantidos internamente em micro-dólares inteiros — as taxas vão até quatro casas decimais, e um número de ponto flutuante não tem lugar em um extrato — e são retornados aqui arredondados para seis.
{ "balance_usd": 412.905400, "currency": "USD" }
Confirmações de entrega
Um recibo é emitido por destinatário conforme a operadora o reporta. Esses são os únicos quatro estados; não existe uma categoria “desconhecido” que silenciosamente signifique “nós o perdemos”.
| Estado | Significado | Cobrado |
|---|---|---|
queued | Aceito e aguardando sua vez no lote. | Sim |
sent | Repassado à operadora, ainda sem confirmação. | Sim |
delivered | O aparelho confirmou o recebimento. | Sim |
undelivered | Desativado, bloqueado, ou o remetente foi recusado. | Reembolsado |
Números que nunca correspondem a um destino com preço definido são rejeitados no envio e nunca entram nesse ciclo de vida — eles não são cobrados e depois estornados, simplesmente não são cobrados.
Webhooks
Defina um endpoint no painel ou por lote com callback_url. Enviamos JSON via POST e esperamos um 2xx em até 5 segundos. Respostas fora do intervalo 2xx são repetidas seis vezes com backoff exponencial ao longo de aproximadamente uma hora, e então descartadas.
Payload
{
"event": "message.delivered",
"batch_id": "b_7Kq2xR9wLm",
"to": "+447700900123",
"destination": "gb",
"segments": 1,
"cost_usd": 0.0137,
"reference": "order-4471",
"ts": 1785312041
}
Verificando a assinatura
Toda entrega carrega X-WorldSMS-Signature e X-WorldSMS-Timestamp. A assinatura é HMAC-SHA256(timestamp + "." + body) usando seu segredo de webhook. Compare em tempo constante e rejeite um timestamp com mais de cinco minutos, para que uma entrega capturada não possa ser reproduzida contra você depois.
// $secret is the webhook secret from the dashboard
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_WORLDSMS_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WORLDSMS_SIGNATURE'] ?? '';
// A stale timestamp is a replay, however well it is signed.
if (abs(time() - (int) $ts) > 300) { http_response_code(408); exit; }
$mine = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($mine, $sig)) { http_response_code(401); exit; }
http_response_code(204);
Seja idempotente
Uma nova tentativa depois que seu endpoint expirou parece idêntica a uma primeira entrega. Use como chave batch_id + to + event e trate uma repetição como uma operação nula.
Erros
Toda falha retorna um corpo JSON com um slug estável em error. Faça a correspondência pelo slug, nunca pelo texto — o texto pode ser aprimorado.
| HTTP | Slug | O que fazer |
|---|---|---|
| 400 | invalid_body | JSON malformado ou um campo obrigatório ausente. |
| 401 | key_rejected | Chave ausente, revogada ou digitada errada. Não é possível tentar novamente. |
| 402 | insufficient_credit | O lote custa mais do que o saldo. Nada foi enviado; recarregue e reenvie. |
| 422 | no_valid_recipients | Nenhum número foi resolvido para um destino com preço. |
| 422 | sender_id_invalid | from tem mais de 11 caracteres ou contém uma string só de dígitos que seria lida como um número. |
| 429 | rate_limited | Reduza o ritmo. Retry-After traz o número de segundos. |
| 503 | route_unavailable | Um destino está temporariamente sem rota. É possível tentar novamente. |
{
"error": "insufficient_credit",
"message": "Batch costs $84.20, balance is $12.05.",
"required_usd": 84.20,
"balance_usd": 12.05
}
Limites
| Solicitações | 60 por minuto por chave, com pico de 20. 429 com Retry-After acima disso. |
|---|---|
| Destinatários por chamada | 5.000. Divida listas maiores entre chamadas, ou envie o arquivo no painel. |
| Tamanho do corpo | 2 MB. |
| Vazão | 200 mensagens por minuto por conta, independentemente de quantas chaves enviarem. |
| Chaves | Sem limite. Um por sistema de envio é o padrão pretendido. |
Os limites de taxa se aplicam às chamadas de API, não às mensagens: uma chamada com 5.000 destinatários custa uma requisição contra o limite.
As chaves são emitidas instantaneamente. Não há etapa de aprovação.
Financie uma conta e o painel cria um na hora.