SDK oficial da API do AR Online (/v3)

v0.3.0 2026-08-20 09:29 UTC

This package is not auto-updated.

Last update: 2026-08-20 09:47:05 UTC


README

CI PHP PHPStan Licença

Cliente oficial da API da AR Online para PHP.

O SDK fala com as duas superfícies da plataforma: a API legada do gateway, que é a que está em produção hoje e concentra envio, status e comprovantes, e a /v3, a API nova, para onde as funcionalidades estão sendo migradas.

Sobre a AR Online

A AR Online é uma plataforma brasileira de notificação eletrônica com validade jurídica. Uma única requisição dispara a notificação em até cinco canais, e cada etapa do percurso — envio, entrega e leitura — é registrada com carimbo do tempo emitido por uma Autoridade de Carimbo do Tempo da ICP-Brasil. Esse registro é o que dá à comunicação o valor de prova documental previsto na MP 2.200-2/2001, e é o que diferencia a plataforma de um serviço comum de disparo de mensagens.

Os canais disponíveis são:

canal o que é
AR-Email e-mail com comprovação de entrega e de leitura
AR-SMS mensagem de texto para o celular do destinatário
AR-WhatsApp notificação por WhatsApp
AR-Voz chamada telefônica automatizada
AR-Cartas carta física registrada, enviada pelos Correios

Você escolhe quais canais usar em cada envio. O processamento é assíncrono: a API confirma o recebimento na hora e devolve um identificador, que você usa depois para consultar o status de cada canal e baixar os comprovantes.

Site https://www.ar-online.com.br
Documentação da API https://docs.ar-online.com.br
Suporte suporte@ar-online.com.br · +55 (11) 4200-7766

Requisitos

  • PHP 8.2 ou mais novo
  • Extensões ext-curl e ext-json
  • Nenhuma dependência de produção além dessas duas extensões

Instalação

composer require aronline/sdk

Autenticação

A plataforma tem duas superfícies de API, e cada uma usa uma credencial diferente. O SDK aceita as duas no mesmo cliente e envia cada uma no formato que a sua superfície espera.

Token do gateway (API legada)

É a credencial que você usa para enviar notificações e consultar status hoje. Solicite em suporte@ar-online.com.br. No SDK, ela vai em legacyToken.

Token da API /v3

Solicite em suporte@ar-online.com.br. O token fica preso a uma entidade da sua conta, e é ela que define quais dados ele enxerga — se você precisa consultar mais de uma, peça um token para cada. O padrão é somente leitura.

O token tem prazo de validade. Token ausente, expirado ou revogado responde 401; se um token vazar, peça a revogação e ele deixa de ser aceito na chamada seguinte.

A /v3 ainda não está publicada. O endereço v3.ar-online.com.br, que é o padrão do SDK para essa superfície, entra no ar junto com ela — assim como a emissão de token por conta própria, na tela Gerar Token da documentação, com o mesmo usuário e senha do portal. Até lá, a parte da /v3 deste SDK serve para desenvolver contra um ambiente de teste, e é $client->legacy() que fala com a API em produção.

Primeiros passos

O envio de notificações é feito hoje pela API legada, exposta no SDK em $client->legacy():

use ArOnline\Sdk\Client;
use ArOnline\Sdk\Legacy\Model\CanalSms;
use ArOnline\Sdk\Legacy\Model\EnvioRequest;

$client = new Client(legacyToken: getenv('AR_GW_TOKEN'));

$envio = $client->legacy()->send(new EnvioRequest(
    nameTo: 'João da Silva',
    subject: 'Notificação de vencimento',
    content: '<p>Prezado João, identificamos uma pendência em seu contrato.</p>',
    to: 'joao@exemplo.com',
    sms: new CanalSms(number: '11999998888'),
));

echo 'notificação aceita: ', $envio->idEmail, PHP_EOL;

Guarde o idEmail: é com ele que você consulta o status de qualquer canal e baixa os comprovantes.

$status = $client->legacy()->status()->email($envio->idEmail);

echo $status->description, PHP_EOL; // Processado, Enviado, Entregue, Lido

Referência

Envio e acompanhamento ($client->legacy())

método o que faz
legacy()->send(EnvioRequest) envia a notificação em um ou mais canais
legacy()->status()->email(id) status do AR-Email
legacy()->status()->sms(id) status do AR-SMS
legacy()->status()->whatsapp(id) status do AR-WhatsApp
legacy()->status()->voz(id) status do AR-Voz
legacy()->status()->carta(id) status do AR-Cartas, com o rastreio dos Correios
legacy()->status()->full(id) dados de perícia de todos os canais numa chamada
legacy()->sendingProof(id) comprovante de envio em PDF
legacy()->laudo(id) laudo pericial em PDF
legacy()->finalizarRegua(id) encerra a régua de notificação do envio
legacy()->templates()->list(GwTemplateType?) lista os modelos da sua entidade
legacy()->templates()->get(id) busca um modelo
legacy()->templates()->update(id, UpdateGwTemplate) edita nome e compartilhamento
legacy()->templates()->deactivate(id) desativa um modelo
legacy()->templates()->setStatus(id, ativo) ativa ou desativa um modelo

Nas cinco rotas de status o id é sempre o uuid do e-mail da notificação — não existe um id por canal para você guardar.

O envio é multicanal: cada canal é um bloco opcional no corpo.

use ArOnline\Sdk\Legacy\Model\Anexo;
use ArOnline\Sdk\Legacy\Model\CanalCarta;
use ArOnline\Sdk\Legacy\Model\CanalSms;
use ArOnline\Sdk\Legacy\Model\CanalVoz;
use ArOnline\Sdk\Legacy\Model\CanalWhatsapp;
use ArOnline\Sdk\Legacy\Model\EnvioRequest;
use ArOnline\Sdk\Legacy\Model\SmsTypeSend;

$client->legacy()->send(new EnvioRequest(
    nameTo: 'João da Silva',
    subject: 'Notificação de vencimento',
    content: '<p>Conteúdo em HTML.</p>',
    to: 'joao@exemplo.com',
    customID: 'contrato-4471', // sua referência, devolvida na consulta de status
    attachments: [new Anexo(name: 'contrato.pdf', base64: '')],
    sms: new CanalSms(
        number: '11999998888',
        typeSend: SmsTypeSend::OnlyIfEmailFails, // Always manda em qualquer caso
        customMessage: 'Você recebeu um AR-Email. Acesse: {SHORT_LINK}',
    ),
    whatsapp: new CanalWhatsapp(number: '11999998888', variables: ['template' => 'aviso_01']),
    voz: new CanalVoz(number: '1133334444', template: 'aviso_voz'),
    carta: new CanalCarta(name: 'João da Silva', modelo: 'padrao'),
));

Comprovantes: o comprovante de envio chega em base64 dentro de um JSON e o SDK já o decodifica; o laudo pericial chega como PDF binário.

$comprovante = $client->legacy()->sendingProof($idEmail);

if ($comprovante->pdf !== null) {
    file_put_contents('comprovante.pdf', $comprovante->pdf);
} else {
    echo $comprovante->message, PHP_EOL; // ainda sem status de entrega
}

file_put_contents('laudo.pdf', $client->legacy()->laudo($idEmail));

As datas do legado

A API antiga tem quatro maneiras de dizer "isto ainda não aconteceu": texto vazio, null, a chave sumindo da resposta e um objeto vazio. Às vezes duas delas na mesma resposta. Em PHP um ?string não distingue null de chave ausente, então os campos de instante são objetos LegacyDate, que carregam o texto e a convenção juntos:

use ArOnline\Sdk\Legacy\Model\DateConvention;

$status = $client->legacy()->status()->whatsapp($idEmail);

if ($status->dateDelivery->happened()) {
    echo $status->dateDelivery->value; // 18/07/2026 01:01:51
}

$status->dateDelivery->convention === DateConvention::MissingKey; // não veio

O valor continua texto, e não DateTimeImmutable: o gateway escreve 18/07/2026 01:01:32 sem fuso, e adivinhar um fuso produziria um instante errado com cara de certo.

Consultas da API /v3 ($client->*)

A /v3 é a API nova, com contrato limpo e validação estrita. Hoje ela é somente de leitura.

método o que faz precisa de token
templates->list(Channel?) lista os modelos, com filtro por canal sim
templates->get(id) busca um modelo pelo UUID sim
tags->list() · tags->get(id) suas etiquetas sim
allowlist->list() seus destinatários permitidos sim
freshness->get() o atraso da carga de dados sim
version->get() qual versão da API está no ar não

Modelos

$todos = $client->templates->list();
$doWhatsApp = $client->templates->list(Channel::WhatsApp);
$um = $client->templates->get('9b2f-uuid');

Channel é um enum com Email, Sms, WhatsApp, Voice e Letter. Um valor fora da lista não passa na análise estática.

Etiquetas e lista de permitidos

$etiquetas = $client->tags->list();
$uma = $client->tags->get('12');
$permitidos = $client->allowlist->list();

São recursos pessoais: respondem o que pertence a quem está no token. Um token de integração, que não representa uma pessoa, recebe 403 nessas rotas.

Atraso da carga

$frescor = $client->freshness->get();

if ($frescor->sourcesBehind > 0) {
    error_log("{$frescor->sourcesBehind} de {$frescor->sourcesTracked} atrasadas");
}

Serve para responder uma pergunta prática: quando uma consulta devolve menos do que você esperava, o problema é a API ou a carga de dados está atrasada?

Versão

$info = $client->version->get();
echo $info->version, ' ', $info->environment;

É a única chamada que funciona sem token, útil para conferir a instalação antes de ter uma credencial.

Tratamento de erros

Chamada que não lançou exceção deu certo. Você não precisa ler status HTTP nem procurar campo de erro no corpo da resposta.

A /v3 lança ApiException:

use ArOnline\Sdk\Exception\ApiException;

try {
    $client->templates->get('nao-existe');
} catch (ApiException $error) {
    echo $error->errorCode;  // 'not_found'
    echo $error->status;     // 404
    echo $error->requestId;  // informe este número ao abrir um chamado
}
propriedade conteúdo
status o status HTTP (0 quando a API não foi alcançada)
errorCode o código do catálogo: not_found, forbidden, rate_limited, …
getMessage() a mensagem da API, em português
requestId identifica a chamada nos nossos registros
field o campo recusado, quando a recusa é sobre um campo
details uma entrada por campo, em erro de validação
retryAfterSeconds quantos segundos esperar, em 429 e 503
isRetryable() true em 429 e 503

Erro de rede e resposta que não é JSON também chegam como ApiException: você trata um catch, não três.

A API legada lança LegacyApiException, com os campos do contrato antigo:

use ArOnline\Sdk\Exception\LegacyApiException;

try {
    $client->legacy()->templates()->get('nao-existe');
} catch (LegacyApiException $error) {
    echo $error->status;      // 404 — o código que vale
    echo $error->httpStatus;  // 200 — o que o protocolo disse
}
propriedade conteúdo
status o código que vale, mesmo quando o HTTP respondeu 200
httpStatus o status que veio no protocolo (0 quando o gateway não foi alcançado)
getMessage() a mensagem do gateway
body o corpo da resposta, exatamente como chegou

Os dois casos em que essa diferença aparece são contrato, não defeito. A família de templates responde 200 com o código de verdade dentro do corpo, e o SDK converte o 403, 404 ou 500 de dentro em exceção. E quando a validação recusa vários campos de uma vez, o gateway manda uma lista de frases no lugar de uma: o SDK junta as frases na mensagem e deixa a lista intacta em body.

O SDK não repete chamadas automaticamente, porque só quem chamou sabe se a operação pode acontecer duas vezes.

Configuração do cliente

new Client(
    token: '',                                    // credencial da /v3
    baseUrl: 'https://v3.ar-online.com.br',        // padrão
    timeout: 30,                                   // padrão, em segundos, vale para as duas
    legacyToken: '',                              // credencial do gateway
    legacyBaseUrl: 'https://api.ar-online.com.br', // padrão
);

Cada credencial é opcional: informe só a da superfície que você vai usar. Uma não vaza na área da outra, e a do gateway vai crua no cabeçalho authorization, sem Bearer, que é o oposto da /v3. Chamar a área de legado sem legacyToken falha antes de sair da máquina, dizendo qual token falta.

As respostas viram objetos readonly$template->name, e não $template['name'] — com a análise estática avisando quando o campo não existe. Campo que a API responde null continua null, nunca vira 0 nem string vazia: um modelo sem assunto não é um modelo com assunto em branco.

Os objetos da área de legado usam os nomes de campo como a API antiga os escreve (customID, idEmail, nome, conteudo, voz, carta). Não há camada de tradução, para que o que você lê no SDK seja o mesmo que você vê na documentação do gateway e nos nossos registros de suporte.

Webhooks

Em vez de consultar o status repetidamente, você pode receber uma chamada POST a cada mudança. A configuração é feita com o suporte, que cadastra o seu endpoint e os parâmetros de autenticação. O SDK não recebe a requisição por você, mas traz os tipos do payload:

use ArOnline\Sdk\Legacy\Model\WebhookPayloadV1;
use ArOnline\Sdk\Legacy\Model\WebhookPayloadV2;

$payload = WebhookPayloadV1::fromArray(json_decode($corpo, true));

No payload v2, o campo payload é o status do canal — decidido pelo campo channel, e não pelo formato: os canais se sobrepõem, e um payload de e-mail satisfaz o formato da voz inteiro.

Veja https://docs.ar-online.com.br/webhooks/visao-geral para o fluxo completo, incluindo a política de retentativas.

As duas superfícies, e o caminho entre elas

A API legada é a que está em produção hoje e concentra envio, status e comprovantes. A /v3 é a API nova, para onde as funcionalidades estão sendo migradas aos poucos.

Quando uma rota ganha equivalente na /v3, o método correspondente de $client->legacy() passa a falar com a /v3 internamente, sem mudar de assinatura. Na prática, você migra atualizando o pacote, não reescrevendo a sua integração. Cada troca dessas é registrada no CHANGELOG.

O equivalente de hoje: a leitura de modelos do gateway tem a /v3 em $client->templates; envio, status e comprovantes ainda não têm.

Desenvolvimento

composer install
composer run verify

verify roda o portão inteiro:

comando o que cobra
composer run format:check PHP-CS-Fixer, PSR-12 com declare(strict_types=1)
composer run lint PHPStan nível 9
composer run spell codespell
composer run test:coverage PHPUnit contra um servidor de teste real
composer run coverage:gate reprova abaixo de 95% de linhas
composer audit vulnerabilidade conhecida em dependência
métrica valor
Testes 89
Asserções 254
Cobertura de linhas 99,82%
Análise estática PHPStan nível 9, limpo
Dependências de produção 0

A medição de cobertura precisa de pcov ou xdebug instalado. Sem um dos dois, test:coverage roda os testes mas não mede nada, e o portão reprova dizendo isso em vez de passar em silêncio. O CI usa pcov.

Os testes sobem um php -S real em uma porta livre e falam com ele por cURL. O que o SDK precisa acertar é o comportamento na rede: qual rota embrulha a resposta, como a recusa volta e o que acontece quando algo que não é a API responde.

Para publicar uma versão, veja PUBLICANDO.md.

Suporte

Ao abrir um chamado sobre uma chamada que falhou, informe o requestId do erro: é com ele que localizamos a requisição nos nossos registros.

Licença

Apache License 2.0 — veja LICENSE.

© 2026 AR ONLINE TECNOLOGIA LTDA.