Do primeiro Pix ao repasse confirmado.
Uma referência completa para conectar seu backend. Explore o contrato, compare exemplos em oito linguagens e execute o fluxo em um laboratório sem movimentar dinheiro.
Manual técnico para integrar o servidor do lojista à Altha One: geração de cobranças, exibição de QR Code, confirmação de pagamentos, recebimento de webhooks, consulta de carteira e acompanhamento de saques e repasses automáticos.
Versão da API: 1. Moeda: BRL. Valores: centavos inteiros. Datas: UTC em ISO 8601.
URL base: https://api.althaone.com.br/api/v1/pix
Baixar OpenAPI · Baixar coleção Postman · Abrir Integração Pix no painel
Esta URL atende à operação LIVE. Os valores e identificadores dos exemplos são fictícios; não são transações de teste. Executar um POST com credencial válida pode criar uma cobrança ou uma solicitação de saque real. O evento webhook.test é sintético e não movimenta dinheiro.
1. Visão geral e arquitetura
Seu sistema conversa exclusivamente com a API Altha One. A Altha One identifica o lojista pela credencial e utiliza as habilitações Pix do cadastro. Sua integração usa apenas a chave de API Altha One e a carteira já existente do lojista.
- Seu backend cria uma cobrança na Altha One e guarda o identificador retornado.
- Seu checkout exibe a imagem do QR Code e o código Pix copia e cola.
- O pagador realiza o pagamento no aplicativo bancário.
- A Altha One recebe a notificação do provedor, consulta a operação e confirma o recebimento financeiro.
- A cobrança passa a
PAID. O líquido correspondente é reservado para uma única intenção de repasse automático. - O repasse é processado para uma chave Pix cadastrada e habilitada, conforme seleção, tarifas e limites existentes.
- Seu webhook recebe os eventos do pagamento e, separadamente, da conclusão do repasse.
Você pode consultar cada recurso a qualquer momento. Webhooks reduzem a necessidade de consultas repetidas, mas a consulta por ID continua disponível para recuperar o estado após falhas de comunicação.
Pagamento recebido e repasse concluído são fatos diferentes. Uma cobrança pode estar PAID enquanto o saque automático está QUEUED, PROCESSING, RECONCILING ou FAILED. Uma falha de saída não desfaz a confirmação da venda.
O módulo atende lojistas ativos com Pix habilitado, incluindo PIX_ONLY e FULL_PAYMENT com Pix. Não há onboarding separado para a API; continuam valendo as capacidades e os controles efetivos do cadastro existente.
2. Primeiros passos no painel
- Entre no painel do próprio lojista e abra Pagamentos → Integração Pix.
- Na visão geral, confira a URL base e as chaves de destino mascaradas. Se nenhuma chave elegível aparecer, ajuste a chave no cadastro existente antes de esperar uma saída financeira.
- Em Chaves de API, escolha um nome que identifique seu servidor e marque apenas as permissões necessárias.
- Clique em Gerar chave, copie o valor integral e guarde-o em um cofre ou variável de ambiente do seu backend.
- Em Webhooks, informe seu endpoint HTTPS, selecione os eventos e ative as entregas. Salve e guarde o segredo exibido.
- Prepare o receptor para validar a assinatura e persistir os eventos. Envie webhook.test pelo painel para conferir esse receptor sem operação financeira.
- Importe a coleção Postman ou siga os exemplos deste guia. Teste inicialmente as consultas de saldo e listagem, sem criar uma operação financeira desnecessária.
O nome da chave serve para sua organização: por exemplo, “ERP produção” ou “Servidor da loja”. A credencial fica vinculada ao lojista e ao ambiente. O corpo das requisições não deve informar Merchant ID nem escolher outro lojista.
O histórico da tela reúne as últimas 20 cobranças e os últimos 20 saques da API. Para intervalos maiores, use os endpoints paginados. O reenvio de uma notificação no painel repete somente a entrega do evento.
3. Credenciais, permissões e segurança
Envie Authorization: Bearer SUA_CHAVE_ALTHA_ONE em todas as operações financeiras. A credencial de integração é própria da Altha One e diferente da sessão do painel. O prefixo de uma chave LIVE é ao_live_.
O valor integral é mostrado uma única vez. A listagem posterior apresenta nome, prefixo, data de criação, último uso e revogação, sem permitir recuperar o segredo original. Se você perder a chave, gere outra e revogue a anterior.
| Permissão | O que permite | Quando selecionar |
|---|---|---|
| charges:write | POST /charges | Servidor que cria cobranças. |
| charges:read | GET /charges e GET /charges/{id} | Checkout, ERP ou rotina de conciliação. |
| withdrawals:write | POST /withdrawals | Servidor autorizado a solicitar saque do saldo disponível. Não é necessária para o repasse automático das cobranças. |
| withdrawals:read | GET /withdrawals e GET /withdrawals/{id} | Acompanhamento de saques manuais e automáticos. |
| balance:read | GET /balance | Consulta da carteira do próprio lojista. |
Uma chave com apenas charges:write pode criar uma cobrança, mas precisa de charges:read para consultá-la depois. Para um checkout com acompanhamento de repasses, normalmente são úteis charges:write, charges:read e withdrawals:read. Adicione a leitura de saldo e a criação de saques somente quando seu produto precisar delas.
O seller ativo e a habilitação Pix são verificados em cada requisição. A revogação bloqueia as próximas requisições; ela não cancela cobranças existentes nem interrompe obrigações financeiras já registradas. A chave pública da API não altera destinos, cadastro bancário, permissões locais nem cria novas credenciais.
Guarde o segredo no backend. Não o coloque em HTML, JavaScript público, aplicativos distribuídos, capturas de tela, URLs, commits ou registros completos de requisições. O navegador do comprador deve receber somente os dados necessários para exibir a cobrança.
Para rotacionar uma credencial: crie outra, instale-a no servidor, confira uma consulta autenticada e revogue a antiga. A chave da API e o segredo do webhook são diferentes; cada um possui sua própria rotação.
4. Convenções do contrato
HTTP e JSON
Use HTTPS e Content-Type: application/json nos POSTs. Envie o objeto diretamente no corpo, sem envelopes como data, payload ou seller. Campos de entrada desconhecidos são rejeitados. Consultas GET não precisam de corpo.
POSTs bem processados pelo endpoint retornam 201 com a intenção persistida. Isso não garante que o provedor criou o QR Code imediatamente nem que um saque foi liquidado. Confira status, error e os campos disponíveis; consulte o mesmo ID para acompanhar o processamento.
Valores monetários
10000 significa R$ 100,00. Use um número inteiro JSON, não "10000", 100.00 como representação de cem reais, nem uma string com vírgula. A API não aceita valores fracionários em centavos. O intervalo estrutural é de 1 a 2.000.000.000 centavos; regras comerciais, saldo e limites efetivos podem impor condições mais restritas.
Datas e identificadores
Datas de resposta são strings ISO 8601 UTC, como 2026-10-31T15:00:00.000Z, ou null quando o fato ainda não ocorreu. Converta apenas para apresentação local; preserve UTC no armazenamento e na comunicação. Os IDs Altha One devem ser tratados como strings opacas, sem inferir significado a partir do formato.
Referência externa e metadados
external_reference correlaciona o recurso com seu pedido ou solicitação. Não é uma chave única e não substitui Idempotency-Key. Dois pedidos com a mesma referência e chaves de idempotência diferentes podem gerar duas operações. Use sua própria política de unicidade no banco local.
metadata é um objeto JSON opcional da cobrança, limitado a 4096 bytes na representação normalizada. Use identificadores de pedido ou canal; evite dados pessoais e segredos. Esses dados e a referência ficam vinculados à cobrança na Altha One.
Dados ausentes e evolução
Campos nulos representam ausência de informação, não valor zero nem reprovação. Uma cobrança ainda em preparação pode ter QR Code vazio e tarifas estimadas nulas. Seu cliente deve aceitar campos adicionais na resposta, conferir os campos que utiliza e preservar o identificador mesmo em processamento pendente.
5. Mapa de endpoints
| Método | Caminho relativo à base | Permissão | Resultado |
|---|---|---|---|
| POST | /charges | charges:write | Cria ou recupera a mesma intenção idempotente de cobrança. |
| GET | /charges | charges:read | Lista cobranças desta integração pertencentes ao lojista. |
| GET | /charges/{id} | charges:read | Consulta uma cobrança e o resumo do seu repasse. |
| POST | /withdrawals | withdrawals:write | Solicita saque para um destino cadastrado; não aceita chave livre. |
| GET | /withdrawals | withdrawals:read | Lista saques manuais da API e repasses automáticos desta integração. |
| GET | /withdrawals/{id} | withdrawals:read | Consulta valor, tarifa, destino mascarado e situação. |
| GET | /balance | balance:read | Consulta somente a carteira interna do lojista. |
Os downloads /openapi.json, /postman.json e a página /docs são públicos e não contêm credenciais nem transações de lojistas. Criação de chaves e configuração de webhook são feitas com a sessão do painel, não com a credencial de integração.
A API pública não oferece edição de cobrança, cancelamento, reembolso ou alteração da chave de destino. O estado CANCELLED existe para representar o recurso quando aplicável; sua existência não significa que há um endpoint público de cancelamento nesta versão.
6. Criar uma cobrança
POST /charges. Permissão: charges:write. Header Idempotency-Key obrigatório.
| Campo | Obrigatório | Tipo e limites | Comportamento |
|---|---|---|---|
| amount_minor | Sim | Número inteiro entre 1 e 2.000.000.000 | Valor bruto da cobrança em centavos. Sujeito aos limites e custos reais. |
| external_reference | Sim | String de 1 a 128 caracteres, sem caracteres de controle | Identificador do pedido no seu sistema; preservado sem deduplicar sozinho. |
| description | Não | String com até 200 caracteres; padrão vazio | Mensagem do pedido, mapeada para payer_message quando enviada ao provedor. |
| expires_in_seconds | Não | Número inteiro de 60 a 86400; padrão 3600 | Prazo solicitado em segundos. Use expires_at da resposta como vencimento registrado. |
| metadata | Não | Objeto JSON, até 4096 bytes; padrão {} | Metadados do seu sistema. Arrays no nível principal e strings não são aceitos como metadata. |
Corpo da criação
json
{
"amount_minor": 10000,
"external_reference": "pedido-123",
"description": "Pagamento do pedido 123",
"expires_in_seconds": 3600,
"metadata": {
"order_id": "123",
"channel": "loja-online"
}
}Criar cobrança com cURL
bash
export ALTHA_API_KEY='SUA_CHAVE_GUARDADA_NO_SERVIDOR'
# Grave esta chave junto ao pedido ANTES da chamada e não a troque em um timeout.
export CHARGE_KEY='pedido-123-cobranca-1'
curl --silent --show-error --max-time 20 \
--request POST 'https://api.althaone.com.br/api/v1/pix/charges' \
--header "Authorization: Bearer $ALTHA_API_KEY" \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $CHARGE_KEY" \
--data '{"amount_minor":10000,"external_reference":"pedido-123","description":"Pagamento do pedido 123","expires_in_seconds":3600,"metadata":{"order_id":"123","channel":"loja-online"}}'Guarde id, a chave de idempotência, o corpo normalizado e a referência no banco do seu sistema. Se a conexão cair, não assuma que a cobrança deixou de existir: repita a solicitação com a mesma chave e o mesmo conteúdo. Não crie outra cobrança apenas porque a imagem do QR Code ainda não apareceu.
Não envie merchant_id, seller_id, chave Pix de recebimento, URL de webhook nem outras credenciais no corpo. O lojista vem da autenticação, o webhook vem da configuração do painel e o processamento usa os vínculos existentes.
7. Entender a resposta da cobrança
O exemplo abaixo ilustra uma cobrança pendente. A tarifa de R$ 1,50 é apenas didática; sua integração deve ler os valores calculados pelo backend, conforme o cadastro do lojista.
Resposta ilustrativa de cobrança pendente
json
{
"id": "11111111-1111-4111-8111-111111111111",
"external_reference": "pedido-123",
"amount_minor": 10000,
"currency": "BRL",
"created_at": "2026-10-31T15:00:00.000Z",
"updated_at": "2026-10-31T15:00:00.000Z",
"source": "API",
"source_charge_id": null,
"error": null,
"status": "PENDING",
"qr_copy_paste": "CONTEUDO_PIX_ILUSTRATIVO_NAO_PAGAVEL",
"qr_image": "data:image/png;base64,IMAGEM_ILUSTRATIVA",
"expires_at": "2026-10-31T16:00:00.000Z",
"paid_at": null,
"automatic_payout": true,
"withdrawal": null,
"commercial": {
"gross_minor": 10000,
"fee_minor": 150,
"net_minor": 9850,
"applied": false
},
"metadata": {
"order_id": "123",
"channel": "loja-online"
}
}| Campo | Tipo | Interpretação |
|---|---|---|
| id | String | Identificador Altha One. Use-o nas consultas e na correlação local. |
| external_reference | String | Referência enviada na criação. |
| status | String | PENDING, PAID, EXPIRED, CANCELLED ou FAILED. |
| amount_minor / currency | Inteiro / BRL | Valor bruto da cobrança e moeda. |
| qr_copy_paste | String | Pix copia e cola. Pode estar vazio enquanto a criação aguarda processamento. |
| qr_image | String data URL PNG ou null | Imagem gerada localmente a partir do copia e cola. Pode ser usada diretamente em img.src. |
| created_at / updated_at | Data UTC | Criação e última atualização da intenção local. updated_at não é comprovante de pagamento. |
| expires_at | Data UTC ou null | Vencimento disponível na cobrança. Pode estar ausente antes do vínculo com o provedor. |
| paid_at | Data UTC ou null | Instante registrado da confirmação financeira. |
| source / source_charge_id | API / null | Cobranças criadas pelo endpoint têm origem API e não derivam de outra cobrança. |
| automatic_payout | Boolean true | O fluxo da nova cobrança prevê intenção automática de repasse; não garante conclusão da saída. |
| withdrawal | Objeto {id,status} ou null | Resumo do repasse vinculado, quando já existir. Consulte /withdrawals/{id} para detalhes. |
| commercial.gross_minor | Inteiro | Valor bruto considerado no cálculo comercial. |
| commercial.fee_minor | Inteiro ou null | Tarifa comercial de recebimento do lojista, distinta do custo interno do provedor. |
| commercial.net_minor | Inteiro ou null | Líquido comercial da venda antes da tarifa de saída. |
| commercial.applied | Boolean | false indica informação ainda estimada; true indica condição aplicada na cobrança confirmada. |
| metadata | Objeto | Metadados preservados da criação. |
| error | Objeto {code,message} ou null | Pendência ou falha do recurso, distinta do envelope de erro HTTP. |
Antes da vinculação da cobrança, commercial.fee_minor e commercial.net_minor podem ser nulos. Não substitua esses valores por uma taxa fixa no frontend. Uma vez disponíveis, os valores de commercial são a fonte de exibição, e applied diferencia estimativa de confirmação.
Exibir o QR Code no checkout
Seu backend entrega ao navegador apenas os campos públicos da cobrança. Use o data URL em uma imagem e o texto do copia e cola em um campo somente leitura. Não converta o código Pix para número nem o altere antes de copiar.
Trecho de frontend sem exposição da chave
html
<img id="pix-image" alt="QR Code para pagamento Pix" width="256" height="256">
<textarea id="pix-code" readonly aria-label="Pix copia e cola"></textarea>
<button id="copy-pix" type="button">Copiar código Pix</button>
<script>
// charge foi obtida do SEU backend, que guarda a credencial Altha One.
function showCharge(charge) {
document.getElementById('pix-image').src = charge.qr_image || '';
document.getElementById('pix-code').value = charge.qr_copy_paste || '';
}
document.getElementById('copy-pix').addEventListener('click', function () {
navigator.clipboard.writeText(document.getElementById('pix-code').value);
});
</script>O conteúdo do exemplo de resposta não gera um QR Code pagável. Use os campos reais devolvidos na criação. Se eles estiverem vazios, informe “Preparando Pix” e consulte a cobrança pelo backend; não gere outra intenção.
8. Consultar e listar cobranças
GET /charges/{id} retorna a representação atual da mesma cobrança, sem criar outro registro. Um ID inexistente ou pertencente a outro lojista retorna recurso inexistente. A referência externa não substitui o ID no caminho.
Consultar uma cobrança
bash
curl --silent --show-error \
'https://api.althaone.com.br/api/v1/pix/charges/11111111-1111-4111-8111-111111111111' \
--header "Authorization: Bearer $ALTHA_API_KEY"GET /charges lista apenas as operações desta integração pertencentes à credencial. A listagem pública não é uma exportação de todas as cobranças históricas criadas por outras telas.
| Parâmetro | Padrão / limite | Regra |
|---|---|---|
| page | 1; de 1 a 10000 | Número da página. Não é cursor. |
| limit | 20; de 1 a 100 | Quantidade máxima de itens por página. |
| from | Opcional | Limite inicial inclusivo da data de criação; envie ISO 8601 UTC com Z. |
| to | Opcional | Limite final inclusivo da data de criação; envie ISO 8601 UTC com Z. |
| status | Opcional | Estado público válido da modalidade. O índice de estado acompanha o worker; a atualização pode ser assíncrona. |
| external_reference | Opcional | Comparação exata da referência, não pesquisa parcial. Codifique o valor na URL. |
Listar cobranças pagas de um período
bash
curl --silent --show-error --get \
'https://api.althaone.com.br/api/v1/pix/charges' \
--header "Authorization: Bearer $ALTHA_API_KEY" \
--data-urlencode 'from=2026-10-01T00:00:00.000Z' \
--data-urlencode 'to=2026-10-31T23:59:59.999Z' \
--data-urlencode 'status=PAID' \
--data-urlencode 'page=1' \
--data-urlencode 'limit=50'Envelope de paginação
json
{
"items": [
{
"id": "11111111-1111-4111-8111-111111111111",
"external_reference": "pedido-123",
"amount_minor": 10000,
"currency": "BRL",
"created_at": "2026-10-31T15:00:00.000Z",
"updated_at": "2026-10-31T15:00:00.000Z",
"source": "API",
"source_charge_id": null,
"error": null,
"status": "PAID",
"qr_copy_paste": "CONTEUDO_PIX_ILUSTRATIVO_NAO_PAGAVEL",
"qr_image": "data:image/png;base64,IMAGEM_ILUSTRATIVA",
"expires_at": "2026-10-31T16:00:00.000Z",
"paid_at": "2026-10-31T15:01:00.000Z",
"automatic_payout": true,
"withdrawal": {
"id": "22222222-2222-4222-8222-222222222222",
"status": "PROCESSING"
},
"commercial": {
"gross_minor": 10000,
"fee_minor": 150,
"net_minor": 9850,
"applied": true
},
"metadata": {
"order_id": "123",
"channel": "loja-online"
}
}
],
"page": 1,
"limit": 50,
"total": 1
}Os itens são ordenados por criação decrescente. total é a quantidade compatível com o filtro e items contém a página atual. Pare quando a quantidade percorrida alcançar o total ou a página vier vazia. Como operações novas podem entrar durante a navegação, deduplique também pelo ID e use janelas de tempo delimitadas para conciliação.
Os filtros de período consideram created_at, não a data de pagamento. Para recuperar uma cobrança criada antes e paga depois, consulte seu ID conhecido ou inclua a data original de criação. Não use apenas “criados hoje” para concluir que não houve pagamento de uma cobrança antiga.
9. Fluxo completo do checkout
- Crie o pedido local: calcule o preço no seu backend e persista o valor em centavos, o dono do pedido e uma referência estável.
- Persista a intenção de cobrança: gere uma chave de idempotência uma única vez e salve-a junto ao pedido antes da chamada HTTP.
- Chame a Altha One: envie o valor do banco, a referência e os metadados necessários. Não use um preço recebido livremente do navegador.
- Guarde o vínculo: salve o
idAltha One no pedido e preserve o corpo original para eventual repetição. - Exiba o Pix: devolva ao navegador QR Code, copia e cola, vencimento e estado; nunca a chave de API.
- Receba a confirmação: valide a assinatura do webhook sobre o corpo bruto e confira o evento, o ID da cobrança, o valor e a moeda.
- Atualize uma única vez: grave o ID único do evento e a mudança do pedido em uma transação local. A liberação do pedido deve seguir
PAID, não apenas a existência de QR Code. - Acompanhe o repasse: associe
pix.withdrawal.completedao pedido pordata.source_charge_id. Mantenha pagamento e repasse em colunas separadas.
Se você entrega um bem ou serviço após o pagamento, dispare essa entrega a partir de uma outbox ou trabalho idempotente do seu próprio sistema. Responder ao webhook antes de gravar a confirmação pode perder eventos; executar uma ação irreversível antes de deduplicá-lo pode repetir a entrega.
Uma tela fechada ou um cliente desconectado não impede a confirmação financeira. O backend e os workers da Altha One processam a operação independentemente do navegador. Use o navegador apenas para apresentar o estado obtido no seu servidor.
Se o pedido expirar comercialmente em sua loja antes de chegar uma confirmação válida, aplique a regra do seu negócio sem apagar o registro financeiro recebido. Não transforme um pagamento confirmado em pendente porque a sessão do comprador terminou.
10. Repasse automático
Novas cobranças criadas por esta API são marcadas para repasse automático. O gatilho ocorre após a confirmação financeira, sem depender de uma sessão aberta ou de um lote diário. A intenção usa os mesmos serviços de carteira, ledger, seleção de destino, reserva e saída do sistema existente.
- A Altha One confirma o recebimento da operação vinculada à cobrança.
- O backend registra o crédito e os valores comerciais corretos.
- Na mesma transação de banco, cria uma única intenção de repasse ligada à cobrança e reserva o valor necessário.
- O worker prepara o destino cadastrado e a saída, preservando o snapshot do destino preparado.
- A saída é enviada e acompanhada. Uma resposta de aceitação do provedor não liquida o saque.
- Somente a consulta com estado financeiro
COMPLETEDpermite apresentar repasse concluído.
Enquanto a intenção automática estiver reservada, o mesmo valor não fica disponível para ser consumido por um saque manual. O crédito da venda e o débito da saída continuam aparecendo nas estruturas financeiras existentes, sem carteira paralela.
A escolha da chave respeita as regras e os destinos do cadastro. O corpo da API não escolhe uma chave livre. Após a preparação, uma alteração posterior no cadastro não muda o destinatário da ordem em processamento.
Quando o pagamento é confirmado e a saída ainda não ocorre
| Situação | O que sua integração deve fazer |
|---|---|
| Destino indisponível ou sem chave elegível | Manter a venda paga; consultar o saque e corrigir o cadastro existente pelo painel. Não pedir outra transferência para contornar a pendência. |
| Limite ou condição operacional do cadastro atingida | Ler error.code/message, manter o vínculo e acompanhar o mesmo ID. |
| Resposta externa incerta | Aguardar conciliação da mesma operação, preservando a reserva. |
| Tarifa alterada antes da preparação da ordem | PRICING_CHANGED: as condições originais são preservadas e a intenção aguarda revisão. Não mudar de chave de idempotência para forçar outra saída. |
| Líquido insuficiente para uma saída válida | O pagamento continua confirmado; a intenção pode falhar com INVALID_DATA. Examine valor, tarifa e situação da carteira. |
| Falha definitiva da saída | Tratar o saque como FAILED e consultar a situação financeira. Uma nova ordem é uma decisão financeira distinta, não um retry cego. |
Não há disparo de saques retroativos em massa de cobranças históricas ao habilitar o módulo. A automação desta integração é vinculada às suas novas cobranças e reutiliza uma ordem correspondente quando já registrada.
“Automático” descreve o processamento sem acionamento manual de rotina. Não é promessa de prazo bancário fixo: indisponibilidade externa, conciliação e condições reais de destino ou limites podem prolongar a conclusão.
11. Tarifas, líquido e valores repassados
Os cálculos são do backend e seguem a tabela comercial do próprio lojista. Não fixe neste cliente um percentual de conversas anteriores, uma tarifa padrão de outro lojista ou um custo interno de processamento.
| Conceito | Campo público / origem | Interpretação |
|---|---|---|
| Bruto da venda | charge.amount_minor e commercial.gross_minor | Valor recebido na cobrança. |
| Tarifa comercial de entrada | commercial.fee_minor | Preço do recebimento cobrado do lojista, conforme sua configuração. |
| Líquido comercial | commercial.net_minor | Bruto menos tarifa comercial de entrada, antes da saída. |
| Reserva contratual | balance.contractual_reserve_minor | Retenção contratual separada quando identificada; não é tarifa de aquisição nem reserva de saída. |
| Reserva para saques | balance.reserved_minor | Valor comprometido com saídas em andamento, inclusive repasse automático. |
| Valor enviado ao recebedor | withdrawal.amount_minor | Quantia destinada à chave Pix. |
| Tarifa comercial de saída | withdrawal.fee_minor | Preço comercial do saque, separado do valor destinado. |
| Débito total | withdrawal.total_debit_minor | Valor destinado mais tarifa comercial de saída. |
| Custos internos e margem Altha One | Contabilidade interna | Não são uma tarifa extra inferida pelo integrador nem campos de saldo central apresentados ao lojista. |
Exemplo didático de repasse
Considere uma venda de R$ 100,00, tarifa comercial de entrada de R$ 1,50 e tarifa comercial de saída de R$ 0,80, sem reserva contratual adicional neste exemplo.
- Bruto:
10000centavos. - Tarifa de entrada:
150centavos. - Líquido comercial:
9850centavos. - Valor da saída automática destinado ao cliente:
9770centavos. - Tarifa de saída:
80centavos. - Débito total da carteira para esse repasse:
9850centavos.
Esses números servem apenas para explicar os campos. O integrador não deve recalcular a tarifa real a partir desse exemplo. Para apresentar o valor final, consulte a cobrança e o saque vinculados. Não desconte a tarifa de saída uma segunda vez de amount_minor: esse campo já é o valor destinado.
Em um saque manual de 5000 centavos com tarifa de 80, o destinatário recebe R$ 50,00 e o débito total é R$ 50,80. Solicitar o saldo disponível inteiro como amount_minor pode falhar porque também é preciso cobrir a tarifa.
O custo do provedor não deve ser somado pelo frontend ao débito comercial retornado. A Altha One trata custo interno e margem em sua contabilidade; os valores comerciais da sua operação são os campos efetivamente retornados.
12. Consultar saldo
GET /balance. Permissão: balance:read. A resposta representa exclusivamente a carteira do lojista autenticado.
Consulta de saldo
bash
curl --silent --show-error \
'https://api.althaone.com.br/api/v1/pix/balance' \
--header "Authorization: Bearer $ALTHA_API_KEY"Resposta ilustrativa de saldo
json
{
"currency": "BRL",
"available_minor": 20000,
"reserved_minor": 9850,
"contractual_reserve_minor": 0,
"other_pending_minor": 0,
"updated_at": "2026-10-31T15:01:05.000Z"
}| Campo | Significado e uso |
|---|---|
| available_minor | Saldo atualmente disponível na carteira. A validação e a reserva da nova saída ocorrem atomicamente no backend; uma consulta anterior não garante saldo futuro. |
| reserved_minor | Valores comprometidos com saques e repasses em andamento. Não podem ser gastos simultaneamente por outra saída. |
| contractual_reserve_minor | Reserva contratual identificada separadamente. A implementação retorna zero quando não existe política distinta nesse ledger. Não deduza que toda conta terá reserva contratual. |
| other_pending_minor | Bucket de valores bloqueados preventivamente; não representa automaticamente o total bruto de cobranças pendentes de pagamento. |
| currency | BRL. |
| updated_at | Instante UTC da leitura; não é necessariamente a data do último lançamento. |
Saldo reservado não é sinônimo de repassado. Uma venda paga pode deixar o saldo disponível próximo de zero porque seu líquido foi reservado imediatamente para saída automática. Consulte a cobrança e o saque para explicar esse cenário ao usuário.
Para obter o valor de cobranças ainda não pagas, use as listagens de cobranças e seus estados. Não trate other_pending_minor como uma agregação de boletos ou Pix ainda não recebidos. Esses campos pertencem exclusivamente à carteira do lojista autenticado.
Duas requisições concorrentes podem ler o mesmo disponível antes da reserva. É o POST de saque que arbitra o consumo; seu sistema deve aceitar a possibilidade de saldo insuficiente, sem tentar contornar a reserva com outra chave.
13. Solicitar e acompanhar saque
POST /withdrawals. Permissão: withdrawals:write. Header Idempotency-Key obrigatório. Essa operação é um saque manual pela API, distinto da saída automática de uma cobrança.
| Campo de entrada | Obrigatório | Formato | Regra |
|---|---|---|---|
| amount_minor | Sim | Inteiro entre 1 e 2.000.000.000 | Valor que o destinatário deve receber, sujeito a saldo, tarifa e limites reais. |
| external_reference | Sim | String de 1 a 128 caracteres, sem controles | Sua referência da solicitação de saque. |
Solicitar saque de R$ 50,00 ao destinatário
bash
# Uma nova ordem financeira precisa de uma intenção local própria.
# Em timeout desta ordem, preserve a mesma chave e o mesmo corpo.
export WITHDRAWAL_KEY='saque-123'
curl --silent --show-error --max-time 20 \
--request POST 'https://api.althaone.com.br/api/v1/pix/withdrawals' \
--header "Authorization: Bearer $ALTHA_API_KEY" \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $WITHDRAWAL_KEY" \
--data '{"amount_minor":5000,"external_reference":"saque-123"}'Não envie destination, pix_key, key, banco ou documento do destinatário. Campos extras são rejeitados. O servidor seleciona uma chave cadastrada e elegível, verifica titularidade e limites e persiste o destino preparado.
A resposta usa o mesmo formato para saída manual e automática. Na saída manual, source é API e source_charge_id é nulo. Na automática, a origem é AUTOMATIC_PAYOUT e o campo aponta para a cobrança Altha One.
Saque automático concluído — resposta ilustrativa
json
{
"id": "22222222-2222-4222-8222-222222222222",
"external_reference": "pedido-123",
"amount_minor": 9770,
"currency": "BRL",
"created_at": "2026-10-31T15:01:00.000Z",
"updated_at": "2026-10-31T15:01:20.000Z",
"source": "AUTOMATIC_PAYOUT",
"source_charge_id": "11111111-1111-4111-8111-111111111111",
"error": null,
"status": "COMPLETED",
"fee_minor": 80,
"total_debit_minor": 9850,
"destination": {
"maskedKey": "l***@exemplo.com",
"keyType": "EMAIL",
"revision": 1,
"beneficiaryId": "33333333-3333-4333-8333-333333333333",
"routeSelectionReason": "PRIMARY",
"authorizationSource": "DOCUMENT_REVIEW"
},
"completed_at": "2026-10-31T15:01:20.000Z",
"end_to_end_id": null
}| Campo | Interpretação |
|---|---|
| id / external_reference | Identificador Altha One da saída e referência preservada. |
| status | QUEUED, PROCESSING, RECONCILING, COMPLETED ou FAILED. |
| source | API para solicitação manual; AUTOMATIC_PAYOUT para saída gerada por uma cobrança desta integração. |
| source_charge_id | ID da cobrança de origem, ou null para saque manual. Use esse vínculo no webhook do repasse. |
| amount_minor | Valor destinado ao recebedor. Não desconte fee_minor novamente. |
| fee_minor | Tarifa comercial da saída. |
| total_debit_minor | amount_minor + fee_minor. |
| destination | Objeto mascarado ou null enquanto a preparação não disponibilizou o destino. |
| destination.maskedKey / keyType | Chave mascarada e tipo da chave; nunca o segredo integral do destino. |
| destination.beneficiaryId / revision | Identificação interna do beneficiário e revisão usada no snapshot. Trate como informação de acompanhamento. |
| destination.routeSelectionReason / authorizationSource | Motivo e fonte registrados pelo fluxo de seleção; podem variar conforme a configuração existente. Não são status financeiro. |
| created_at / updated_at | Datas UTC da intenção. updated_at não comprova liquidação. |
| completed_at | Data UTC da conclusão financeira, ou null. |
| end_to_end_id | Rastreio Pix quando persistido; pode ser null e sua presença isolada não confirma liquidação. |
| error | Código e mensagem da pendência/falha, ou null. |
Consultar a saída e listar seu histórico
bash
curl --silent --show-error \
'https://api.althaone.com.br/api/v1/pix/withdrawals/22222222-2222-4222-8222-222222222222' \
--header "Authorization: Bearer $ALTHA_API_KEY"
curl --silent --show-error --get \
'https://api.althaone.com.br/api/v1/pix/withdrawals' \
--header "Authorization: Bearer $ALTHA_API_KEY" \
--data-urlencode 'external_reference=pedido-123' \
--data-urlencode 'page=1' \
--data-urlencode 'limit=20'A listagem de saques aceita os mesmos filtros de período e paginação, com os estados próprios de saída. Ela inclui repasses automáticos desta integração e saques pedidos pela API. Não use a inexistência de uma saída nessa listagem para concluir que não há nenhum saque histórico criado por outro fluxo do painel.
14. Estados e transições
Cobrança
| Estado | O que significa | Ação do integrador |
|---|---|---|
| PENDING | Cobrança em preparação ou aguardando confirmação financeira. | Exibir QR quando disponível; aguardar confirmação ou consultar o ID. |
| PAID | Recebimento confirmado. | Marcar o pedido pago uma única vez, conferindo identidade, valor e moeda. Acompanhar o repasse separadamente. |
| EXPIRED | Cobrança identificada como expirada pelo processamento/conciliação. | Encerrar a apresentação daquele QR conforme seu negócio. Se precisar cobrar de novo, registrar uma nova intenção consciente. |
| CANCELLED | Cancelamento representado pelo estado interno. | Não considerar paga. Não há endpoint público de cancelamento nesta versão. |
| FAILED | Falha definitiva representada na cobrança. | Ler erro e corrigir a causa; não executar retries financeiros cegos. |
Fluxo comum: PENDING → PAID, PENDING → EXPIRED ou PENDING → FAILED. A atualização depende do estado apurado e pode ocorrer por webhook ou conciliação. Seu consumidor não deve regredir um pedido pago ao receber uma fotografia antiga pendente.
Saque e repasse automático
| Estado | O que significa | Ação do integrador |
|---|---|---|
| QUEUED | Intenção registrada, aguardando preparação ou condições elegíveis. | Guardar o ID e ler error caso exista; não solicitar outra saída. |
| PROCESSING | Saída em processamento, ainda sem conclusão financeira. | Acompanhar por webhook e consulta. |
| RECONCILING | Resultado exige conciliação ou análise da mesma operação. | Manter a intenção, sem nova chave financeira. Não liberar a mesma quantia como se a saída tivesse falhado. |
| COMPLETED | Conclusão financeira confirmada. | Marcar repasse concluído uma única vez. |
| FAILED | Falha definitiva ou cancelamento anterior ao despacho representado pelo fluxo. | Ler o diagnóstico e verificar saldo e obrigação antes de qualquer nova ordem financeira. |
HTTP 201, 202, um QR Code ou um identificador de rastreio não significam que houve conclusão de saque. Os estados de pagamento e de saída são independentes; nunca use um mesmo campo local para representar os dois.
15. Idempotência, concorrência e timeout
O header Idempotency-Key é obrigatório nos dois POSTs financeiros. Ele aceita de 1 a 128 caracteres entre letras, números, sublinhado, ponto, dois-pontos e hífen: ^[A-Za-z0-9_.:-]{1,128}$.
O escopo é o lojista, o ambiente e o tipo da operação. A mesma chave pode existir em lojistas diferentes sem compartilhar uma operação. A Altha One cria uma identidade própria para o provedor; não encaminha diretamente sua chave como identificador global da conta central.
| Cenário | Resultado esperado |
|---|---|
| Mesma chave e mesmo conteúdo normalizado | Retorna a mesma operação Altha One, podendo apresentar seu estado atualizado. |
| Mesma chave e outro valor, referência ou metadado relevante | HTTP 409 IDEMPOTENCY_CONFLICT; não cria outra operação. |
| Mesma referência externa e outra chave | Pode criar outra intenção. A referência externa não deduplica a requisição. |
| Duas chamadas simultâneas da mesma intenção | Controle de concorrência e unicidade no banco preservam uma única intenção. |
| Conexão caiu antes da resposta | Resultado desconhecido para o cliente. Consulte o ID se já o conhece; caso contrário, repita com a chave e corpo originais. |
| Saque em RECONCILING | Acompanhe a mesma operação. Outra chave não é uma forma segura de retry. |
Como guardar a chave no seu sistema
Gere a chave quando seu pedido ou solicitação de saída for persistido. Armazene-a junto ao valor, referência, corpo do pedido e ID Altha One. Recarregar a página, reiniciar o processo ou receber um timeout não deve gerar uma nova chave.
A comparação considera o conteúdo normalizado. Por exemplo, campos opcionais da cobrança ausentes recebem os padrões documentados; a ordem das chaves de um objeto JSON não identifica um novo pedido. Mudar o conteúdo de metadata na repetição é uma alteração relevante e pode gerar conflito.
Procedimento após timeout
- Marque localmente a tentativa como aguardando resultado, preservando o pedido original.
- Se recebeu um ID antes da falha, consulte-o.
- Se não recebeu ID, repita o mesmo POST com a mesma chave e o mesmo corpo.
- Se houver estado incerto ou conciliação, continue consultando essa operação com intervalos progressivos.
- Não peça a mesma saída com outra chave para “destravar”. Só registre outra ordem após entender a falha definitiva e a obrigação financeira remanescente.
Não espere que a resposta de uma repetição seja byte a byte igual à primeira: o ID é o mesmo, mas status, datas de atualização e detalhes do repasse podem ter evoluído. Preserve as condições e a identidade da intenção original.
16. Configurar webhooks no painel
Esta versão possui uma URL principal por lojista. Abra Integração Pix → Webhooks, preencha a URL, selecione os eventos, marque Ativar entregas e salve. A chave de API não cria ou altera essa configuração.
A URL deve usar HTTPS na porta padrão 443, certificado válido, sem usuário/senha embutidos nem fragmento. Nesta versão, o destino precisa resolver exclusivamente para endereços IPv4 públicos permitidos. Destinos IPv6, redes privadas, loopback, link-local, endereços de metadados e redirecionamentos são bloqueados. Se seu endpoint muda de endereço, a proteção é refeita nas tentativas de entrega.
Não use localhost ou o endereço interno do container. Exponha um endpoint HTTPS público do seu servidor, diretamente na URL que aceita POST; um redirecionamento de HTTP para HTTPS ou entre caminhos não é uma entrega aceita.
Segredo de assinatura
Copie o segredo mostrado ao criar a configuração e guarde-o como ALTHA_WEBHOOK_SECRET no receptor. A listagem posterior não recupera o valor integral. Ele é diferente da chave Bearer usada nas consultas.
Ao rotacionar, o novo segredo passa a assinar as próximas tentativas, inclusive reentregas de eventos antigos. Coordene a troca no seu servidor; a API não oferece uma janela contratada de assinatura simultânea com dois segredos. Pausar temporariamente as entregas durante a troca permite ajustar o receptor e retomá-las depois.
Evento de teste
Selecione webhook.test, mantenha a configuração ativa e clique em Enviar evento de teste. Confira a entrega no histórico e valide assinatura, corpo bruto e ID. O botão gera um evento durável sintético; não cria cobrança, crédito, saque nem transferência.
Desmarcar um tipo de evento suspende suas entregas; não desfaz a operação financeira. Eventos pendentes elegíveis podem voltar a ser entregues quando a configuração for retomada. O histórico exibe até 100 eventos recentes e permite reenviar uma notificação sem repetir o fato financeiro.
17. Envelope, eventos e assinatura do webhook
A Altha One faz um POST JSON na URL configurada. O envelope possui as seguintes propriedades:
| Campo | Regra |
|---|---|
| id | ID único do evento com prefixo evt_. Use-o para deduplicação durável. |
| type | Tipo do evento da tabela abaixo. |
| created_at | Criação do evento em UTC. Não é o timestamp da tentativa de entrega. |
| api_version | String "1". |
| data | Fotografia do recurso no momento da criação do evento, ou dados sintéticos para webhook.test. |
| Evento | Conteúdo / interpretação |
|---|---|
| pix.charge.created | Recurso de cobrança registrado no fluxo. O QR pode ainda estar em preparação; confira data.status. |
| pix.charge.paid | Cobrança paga, com data.status=PAID e valores comerciais confirmados. |
| pix.charge.expired | Cobrança identificada como expirada. |
| pix.charge.failed | Cobrança com falha definitiva registrada. |
| pix.withdrawal.processing | Intenção de saída registrada/em acompanhamento. Confira data.status: o marco não garante que o provedor já iniciou o envio. |
| pix.withdrawal.completed | Saída com conclusão financeira confirmada; source_charge_id identifica a cobrança automática de origem. |
| pix.withdrawal.failed | Saída com falha definitiva registrada. |
| webhook.test | Objeto sintético com synthetic=true, sem operação financeira. |
Exemplo de evento de pagamento
json
{
"id": "evt_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"type": "pix.charge.paid",
"created_at": "2026-10-31T15:01:01.000Z",
"api_version": "1",
"data": {
"id": "11111111-1111-4111-8111-111111111111",
"external_reference": "pedido-123",
"amount_minor": 10000,
"currency": "BRL",
"created_at": "2026-10-31T15:00:00.000Z",
"updated_at": "2026-10-31T15:00:00.000Z",
"source": "API",
"source_charge_id": null,
"error": null,
"status": "PAID",
"qr_copy_paste": "CONTEUDO_PIX_ILUSTRATIVO_NAO_PAGAVEL",
"qr_image": "data:image/png;base64,IMAGEM_ILUSTRATIVA",
"expires_at": "2026-10-31T16:00:00.000Z",
"paid_at": "2026-10-31T15:01:00.000Z",
"automatic_payout": true,
"withdrawal": {
"id": "22222222-2222-4222-8222-222222222222",
"status": "QUEUED"
},
"commercial": {
"gross_minor": 10000,
"fee_minor": 150,
"net_minor": 9850,
"applied": true
},
"metadata": {
"order_id": "123",
"channel": "loja-online"
}
}
}Exemplo de evento de repasse automático
json
{
"id": "evt_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"type": "pix.withdrawal.completed",
"created_at": "2026-10-31T15:01:21.000Z",
"api_version": "1",
"data": {
"id": "22222222-2222-4222-8222-222222222222",
"external_reference": "pedido-123",
"amount_minor": 9770,
"currency": "BRL",
"created_at": "2026-10-31T15:01:00.000Z",
"updated_at": "2026-10-31T15:01:20.000Z",
"source": "AUTOMATIC_PAYOUT",
"source_charge_id": "11111111-1111-4111-8111-111111111111",
"error": null,
"status": "COMPLETED",
"fee_minor": 80,
"total_debit_minor": 9850,
"destination": {
"maskedKey": "l***@exemplo.com",
"keyType": "EMAIL",
"revision": 1,
"beneficiaryId": "33333333-3333-4333-8333-333333333333",
"routeSelectionReason": "PRIMARY",
"authorizationSource": "DOCUMENT_REVIEW"
},
"completed_at": "2026-10-31T15:01:20.000Z",
"end_to_end_id": null
}
}Não espere um evento para cada mudança intermediária. Existem eventos de início/acompanhamento e de resultado final; se precisar do estado atual durante o processamento, consulte o recurso por ID. Não há evento público específico pix.charge.cancelled nesta versão.
Headers da entrega
| Header | Conteúdo |
|---|---|
| Content-Type | application/json |
| X-AlthaOne-Event-Id | Mesmo ID do campo id do envelope. |
| X-AlthaOne-Timestamp | Segundos Unix da tentativa atual, representados como string. |
| X-AlthaOne-Signature | sha256= seguido de 64 caracteres hexadecimais minúsculos. |
Cálculo e validação
Calcule HMAC-SHA256(segredo, timestamp + "." + corpo_bruto) e compare a representação sha256=<hex> em tempo constante. O corpo usado no HMAC é a sequência original de bytes recebida. Fazer JSON.parse e depois JSON.stringify pode alterar espaços e ordem, invalidando a assinatura.
- Capture o corpo bruto antes do parser JSON do framework.
- Valide o formato do timestamp e aplique uma tolerância local, por exemplo 300 segundos, mantendo o relógio do servidor sincronizado.
- Calcule e compare a assinatura em tempo constante.
- Faça o parse JSON e confira se o ID do envelope é igual ao header.
- Deduplicate o ID do evento e atualize o pedido na mesma transação local.
- Responda 2xx apenas após persistir o resultado ou enfileirar de forma durável no seu sistema.
Um teste de timestamp é uma política do seu receptor, não uma garantia de que o evento financeiro foi criado há poucos segundos. Em uma reentrega, created_at continua antigo e o timestamp da assinatura é novo.
Para marcar um pedido pago, confira também data.status, data.id, data.external_reference, data.amount_minor e data.currency contra seu registro local. A assinatura comprova a origem da mensagem; a correlação correta impede liberar o pedido errado.
18. Entregas, retentativas e eventos fora de ordem
Qualquer HTTP 2xx é aceito como confirmação de entrega. A resposta não precisa de JSON. HTTP 3xx não é seguido como redirecionamento; 4xx, 5xx, falha de rede e timeout geram tentativa posterior. Cada chamada tem timeout de 10 segundos.
O limite automático é de oito tentativas. A espera após falha cresce por min(3600s, 5s × 2^n), com n iniciado em zero. Para as oito tentativas automáticas desta versão, os intervalos mínimos entre a primeira e as próximas são 5, 10, 20, 40, 80, 160 e 320 segundos. Scheduler, indisponibilidade ou fila podem acrescentar atraso; esses intervalos não são um SLA.
O ID e o conteúdo do evento permanecem os mesmos em uma reentrega. A assinatura e o timestamp são recalculados a cada tentativa. Se seu servidor persistir a atualização e falhar antes de devolver 2xx, a mesma mensagem pode chegar novamente: sua deduplicação precisa sobreviver a reinícios, não apenas ficar em memória.
Ordem e fotografias do recurso
Não há garantia de ordem entre eventos de cobrança e saída, nem entre uma entrega nova e um reenvio manual antigo. Um pix.charge.created antigo pode chegar após pix.charge.paid. Preserve fatos confirmados e consulte o ID quando houver dúvida.
Não use o conteúdo de um evento antigo para substituir integralmente seu registro mais recente. Marque o pagamento e a conclusão do repasse em campos independentes e idempotentes. Um evento de saque pode chegar antes de o processo que criou a cobrança terminar de guardar o ID local; a referência externa persistida antes da chamada ajuda a recuperar o vínculo.
Falha de entrega e reenvio
No histórico do painel, veja a quantidade de tentativas, o último HTTP e a situação. Depois de corrigir o receptor, use Reenviar notificação. Esse botão preserva evento e conteúdo, reinicia o ciclo de entrega e não cria cobrança, crédito ou saque.
Uma URL pausada ou um evento desmarcado não bloqueia o recebimento financeiro. Os workers de entrega e processamento financeiro são separados. Se seu servidor precisa responder rápido, persista o envelope bruto validado em uma inbox e processe-o depois, com deduplicação; não responda sucesso antes dessa persistência.
19. Implementação Node.js e PostgreSQL
Os arquivos abaixo são uma referência executável de transporte e recebimento. Use Node.js 20 ou superior, módulos ES e as bibliotecas express e pg. O banco e a autenticação dos compradores pertencem ao seu sistema; os nomes das tabelas abaixo são apenas exemplos locais, não tabelas da Altha One.
Instale as dependências no seu projeto com npm install express pg. Configure ALTHA_API_KEY, ALTHA_WEBHOOK_SECRET e DATABASE_URL no ambiente do servidor. Os exemplos não executam operações financeiras quando apenas importados.
Arquivo pix-client.mjs — transporte reutilizável
javascript
export class PixError extends Error {
constructor(message, details = {}) {
super(message);
Object.assign(this, details);
}
}
export async function pix(path, {method = 'GET', body, key} = {}) {
const token = process.env.ALTHA_API_KEY;
if (!token) throw new Error('Configure ALTHA_API_KEY no servidor');
const headers = {Authorization: 'Bearer ' + token, Accept: 'application/json'};
if (body !== undefined) headers['Content-Type'] = 'application/json';
if (key) headers['Idempotency-Key'] = key;
let response;
try {
response = await fetch('https://api.althaone.com.br/api/v1/pix' + path, {
method, headers, signal: AbortSignal.timeout(15000),
...(body === undefined ? {} : {body: JSON.stringify(body)})
});
} catch {
throw new PixError('Falha de comunicação; preserve a intenção original', {
code: 'NETWORK_UNCERTAIN', uncertain: method === 'POST'
});
}
const requestId = response.headers.get('X-Request-Id');
let data;
try { data = await response.json(); }
catch {
throw new PixError('Resposta não JSON; consulte a mesma intenção', {
code: 'INVALID_RESPONSE', http: response.status, requestId,
uncertain: method === 'POST'
});
}
if (!response.ok) throw new PixError(data.error?.message || 'Erro na API', {
code: data.error?.code, http: response.status,
requestId: data.error?.request_id || requestId,
uncertain: method === 'POST' && response.status >= 500
});
return {data, requestId};
}Arquivo operations.mjs — operações que seu backend pode chamar
javascript
import {pix} from './pix-client.mjs';
// persistedKey vem do seu banco; não gere outra em cada chamada.
export async function createCharge(order, persistedKey) {
return pix('/charges', {method:'POST', key:persistedKey, body:{
amount_minor:order.amount_minor,
external_reference:order.external_reference,
description:'Pagamento do pedido ' + order.external_reference,
expires_in_seconds:3600,
metadata:{order_id:order.id}
}});
}
export async function getCharge(id) {
return pix('/charges/' + encodeURIComponent(id));
}
export async function listCharges({from, to, page = 1, limit = 20}) {
const q = new URLSearchParams({from, to, page:String(page), limit:String(limit)});
return pix('/charges?' + q);
}
export async function getBalance() { return pix('/balance'); }
// Chame apenas por uma ordem financeira autorizada no SEU sistema.
// amountMinor é o valor destinado; a tarifa é adicional no débito da carteira.
export async function requestWithdrawal(amountMinor, reference, persistedKey) {
return pix('/withdrawals', {method:'POST', key:persistedKey, body:{
amount_minor:amountMinor, external_reference:reference
}});
}
export async function getWithdrawal(id) {
return pix('/withdrawals/' + encodeURIComponent(id));
}
// Não existe retry automático neste cliente: persista e acompanhe a mesma intenção.Tabelas de exemplo no banco do integrador
sql
CREATE TABLE integration_orders (
id text PRIMARY KEY,
customer_id text NOT NULL,
external_reference text UNIQUE NOT NULL,
amount_minor bigint NOT NULL CHECK (amount_minor > 0),
currency text NOT NULL DEFAULT 'BRL',
idempotency_key text UNIQUE NOT NULL,
altha_charge_id text UNIQUE,
paid_at timestamptz,
payout_completed_at timestamptz
);
CREATE TABLE integration_received_events (
id text PRIMARY KEY,
event_type text NOT NULL,
received_at timestamptz NOT NULL DEFAULT now()
);
-- Cadastre o pedido real e sua chave de idempotência ANTES de chamar a API.
-- Não registre segredos de API nem dados bancários nessas tabelas.Arquivo checkout.mjs — pedido persistido e correlação
javascript
import pg from 'pg';
import {createCharge, getCharge} from './operations.mjs';
const pool = new pg.Pool({connectionString:process.env.DATABASE_URL});
// authenticatedCustomerId deve vir da sessão autenticada do SEU sistema.
// O pedido, seu valor e idempotency_key já existem no banco.
export async function checkoutPix(orderId, authenticatedCustomerId) {
const found = await pool.query(
'SELECT * FROM integration_orders WHERE id=$1 AND customer_id=$2',
[orderId, authenticatedCustomerId]
);
if (!found.rowCount) throw new Error('Pedido não encontrado');
const order = found.rows[0];
const amountMinor = Number(order.amount_minor);
if (!Number.isSafeInteger(amountMinor)) throw new Error('Valor inválido no pedido');
const result = order.altha_charge_id
? await getCharge(order.altha_charge_id)
: await createCharge({
id:order.id, amount_minor:amountMinor,
external_reference:order.external_reference
}, order.idempotency_key);
const charge = result.data;
if (charge.amount_minor !== amountMinor || charge.currency !== order.currency)
throw new Error('Divergência entre cobrança e pedido');
const saved = await pool.query(
'UPDATE integration_orders SET altha_charge_id=$1 WHERE id=$2 AND (altha_charge_id IS NULL OR altha_charge_id=$1)',
[charge.id, order.id]
);
if (!saved.rowCount) throw new Error('Pedido já vinculado a outra cobrança');
// Retorne estes dados ao navegador; não inclua sua chave nem o segredo do webhook.
return {
id:charge.id, status:charge.status, qr_image:charge.qr_image,
qr_copy_paste:charge.qr_copy_paste, expires_at:charge.expires_at
};
}Esse exemplo não mantém uma transação local aberta durante o HTTP. Chamadas concorrentes usam a chave já persistida; a atualização final não substitui um ID diferente. Sua rota de checkout deve aplicar a sessão e as regras do seu próprio sistema antes de chamar a função.
Receptor de webhook com corpo bruto e deduplicação transacional
O código a seguir trata pagamento e repasse separadamente. O pedido é localizado pela referência exata persistida e validado pelo ID, valor e moeda. Isso permite vincular o pagamento mesmo se o webhook chegar antes de o checkout guardar a resposta de criação.
Arquivo webhook.mjs — Express, HMAC e PostgreSQL
javascript
import express from 'express';
import {createHmac, timingSafeEqual} from 'node:crypto';
import pg from 'pg';
const app = express();
const pool = new pg.Pool({connectionString:process.env.DATABASE_URL});
const secret = process.env.ALTHA_WEBHOOK_SECRET;
if (!secret) throw new Error('Configure ALTHA_WEBHOOK_SECRET');
// Esta rota deve ser registrada ANTES de app.use(express.json()).
app.post('/webhooks/altha-one', express.raw({type:'application/json', limit:'1mb'}), async (req, res) => {
const timestamp = req.get('X-AlthaOne-Timestamp') || '';
const supplied = req.get('X-AlthaOne-Signature') || '';
if (!Buffer.isBuffer(req.body) || !/^\d{1,12}$/.test(timestamp) ||
Math.abs(Date.now()/1000 - Number(timestamp)) > 300 ||
!/^sha256=[a-f0-9]{64}$/.test(supplied)) return res.sendStatus(401);
const expected = 'sha256=' + createHmac('sha256', secret)
.update(timestamp + '.').update(req.body).digest('hex');
if (!timingSafeEqual(Buffer.from(supplied), Buffer.from(expected)))
return res.sendStatus(401);
let event;
try { event = JSON.parse(req.body.toString('utf8')); }
catch { return res.sendStatus(400); }
if (!event || typeof event.id !== 'string' || !event.id.startsWith('evt_') ||
event.id !== req.get('X-AlthaOne-Event-Id') || event.api_version !== '1' ||
typeof event.type !== 'string' || !event.data) return res.sendStatus(400);
let client;
try {
client = await pool.connect();
await client.query('BEGIN');
const inserted = await client.query(
'INSERT INTO integration_received_events(id,event_type) VALUES($1,$2) ON CONFLICT DO NOTHING RETURNING id',
[event.id,event.type]
);
if (!inserted.rowCount) { await client.query('COMMIT'); return res.sendStatus(204); }
const d = event.data;
if (event.type === 'pix.charge.paid') {
if (d.status !== 'PAID' || d.currency !== 'BRL' || !Number.isSafeInteger(d.amount_minor))
throw new Error('Pagamento incompatível');
const order = await client.query(
'SELECT * FROM integration_orders WHERE external_reference=$1 FOR UPDATE',
[d.external_reference]
);
const o = order.rows[0];
if (!o || Number(o.amount_minor) !== d.amount_minor || o.currency !== d.currency ||
(o.altha_charge_id && o.altha_charge_id !== d.id))
throw new Error('Cobrança não corresponde ao pedido');
await client.query(
'UPDATE integration_orders SET altha_charge_id=$1, paid_at=COALESCE(paid_at,now()) WHERE id=$2',
[d.id,o.id]
);
// Grave aqui uma outbox local se sua entrega de produto exige processamento posterior.
}
if (event.type === 'pix.withdrawal.completed' && d.source === 'AUTOMATIC_PAYOUT') {
if (d.status !== 'COMPLETED' || !d.source_charge_id) throw new Error('Repasse incompatível');
const order = await client.query(
'SELECT * FROM integration_orders WHERE external_reference=$1 FOR UPDATE',
[d.external_reference]
);
const o = order.rows[0];
if (!o || (o.altha_charge_id && o.altha_charge_id !== d.source_charge_id))
throw new Error('Repasse não corresponde ao pedido');
await client.query(
'UPDATE integration_orders SET altha_charge_id=$1, payout_completed_at=COALESCE(payout_completed_at,now()) WHERE id=$2',
[d.source_charge_id,o.id]
);
// Não marque paid_at aqui: a confirmação de pagamento possui seu próprio evento.
}
// Eventos informativos e webhook.test são deduplicados e aceitos sem liberar pedido.
await client.query('COMMIT');
return res.sendStatus(204);
} catch {
if (client) await client.query('ROLLBACK').catch(() => {});
// Registre apenas identificadores/diagnóstico sanitizado; nunca o segredo.
return res.sendStatus(503);
} finally { client?.release(); }
});
app.use(express.json());
app.listen(3000);O receptor acima presume que esse webhook trata os pedidos da mesma aplicação que grava integration_orders. Se o lojista usa vários sistemas ou também faz saques manuais, encaminhe cada recurso ao domínio correto antes de alterar pedidos. Eventos desconhecidos podem ser registrados e aceitos; eventos com divergência financeira conhecida devem ser investigados.
Em produção, mantenha logs com ID do evento e código da falha, monitore as respostas 503 e implemente conciliação para casos que ultrapassem o limite de tentativas. A deduplicação por evento complementa a idempotência da atualização do pedido.
20. Implementação PHP e PostgreSQL
Use PHP 8.1 ou superior com extensões cURL e PDO PostgreSQL. Reaproveite as tabelas locais da seção anterior ou adapte os nomes ao seu sistema. Configure os segredos em variáveis de ambiente; não coloque credenciais reais no arquivo publicado.
Arquivo pix-client.php — transporte e operações
php
<?php
declare(strict_types=1);
function althaPix(string $path, ?array $body=null, ?string $key=null): array {
$token=getenv('ALTHA_API_KEY');
if (!$token) throw new RuntimeException('Configure ALTHA_API_KEY');
$ch=curl_init('https://api.althaone.com.br/api/v1/pix'.$path);
$headers=['Authorization: Bearer '.$token, 'Accept: application/json'];
if ($body!==null) $headers[]='Content-Type: application/json';
if ($key!==null) $headers[]='Idempotency-Key: '.$key;
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER=>true, CURLOPT_HTTPHEADER=>$headers,
CURLOPT_CONNECTTIMEOUT=>5, CURLOPT_TIMEOUT=>15, CURLOPT_FOLLOWLOCATION=>false
]);
if ($body!==null) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_THROW_ON_ERROR));
}
$raw=curl_exec($ch);
$http=(int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($raw===false) {
curl_close($ch);
throw new RuntimeException('Resultado de comunicação incerto; preserve chave e corpo');
}
curl_close($ch);
$data=json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
if ($http<200 || $http>=300) {
throw new RuntimeException(
($data['error']['code']??'HTTP_ERROR').' | request_id='.
($data['error']['request_id']??'não informado').' | HTTP '.$http
);
}
return $data;
}
function criarCobranca(array $pedido, string $chavePersistida): array {
return althaPix('/charges', [
'amount_minor'=>$pedido['amount_minor'],
'external_reference'=>$pedido['external_reference'],
'description'=>'Pagamento do pedido '.$pedido['external_reference'],
'expires_in_seconds'=>3600,
'metadata'=>['order_id'=>$pedido['id']]
], $chavePersistida);
}
function consultarCobranca(string $id): array {
return althaPix('/charges/'.rawurlencode($id));
}
function listarCobrancas(string $inicio, string $fim, int $pagina=1): array {
return althaPix('/charges?'.http_build_query([
'from'=>$inicio,'to'=>$fim,'page'=>$pagina,'limit'=>20
]));
}
function consultarSaldo(): array { return althaPix('/balance'); }
function solicitarSaque(int $valorDestinado, string $referencia, string $chavePersistida): array {
return althaPix('/withdrawals', [
'amount_minor'=>$valorDestinado,'external_reference'=>$referencia
], $chavePersistida);
}
function consultarSaque(string $id): array {
return althaPix('/withdrawals/'.rawurlencode($id));
}
// As funções não disparam operações ao incluir o arquivo.
// Chame criarCobranca/solicitarSaque somente a partir de uma intenção real persistida.Arquivo checkout.php — vínculo seguro ao pedido existente
php
<?php
declare(strict_types=1);
require_once __DIR__.'/pix-client.php';
// $pdo deve vir da conexão PostgreSQL já configurada no seu sistema.
// $customerId vem da sessão autenticada, nunca de um campo livre do navegador.
function checkoutPix(PDO $pdo, string $orderId, string $customerId): array {
$q=$pdo->prepare('SELECT * FROM integration_orders WHERE id=? AND customer_id=?');
$q->execute([$orderId,$customerId]);
$o=$q->fetch(PDO::FETCH_ASSOC);
if (!$o) throw new RuntimeException('Pedido não encontrado');
$charge=$o['altha_charge_id']
? consultarCobranca($o['altha_charge_id'])
: criarCobranca([
'id'=>$o['id'],'external_reference'=>$o['external_reference'],
'amount_minor'=>(int)$o['amount_minor']
],$o['idempotency_key']);
if ($charge['amount_minor']!==(int)$o['amount_minor'] || $charge['currency']!==$o['currency'])
throw new RuntimeException('Cobrança divergente');
$save=$pdo->prepare('UPDATE integration_orders SET altha_charge_id=? WHERE id=? AND (altha_charge_id IS NULL OR altha_charge_id=?)');
$save->execute([$charge['id'],$o['id'],$charge['id']]);
if (!$save->rowCount()) throw new RuntimeException('Pedido já vinculado a outra cobrança');
return [
'id'=>$charge['id'],'status'=>$charge['status'],
'qr_image'=>$charge['qr_image'],'qr_copy_paste'=>$charge['qr_copy_paste'],
'expires_at'=>$charge['expires_at']
];
}Ao montar o HTML no PHP, escape atributos e textos com htmlspecialchars. Não imprima a credencial junto à resposta do checkout. O exemplo de webhook a seguir é um arquivo separado, sem chamar criação de cobrança ou saque.
Arquivo webhook.php — assinatura e deduplicação atômica
php
<?php
declare(strict_types=1);
// Configure PDO_DSN, PDO_USER e PDO_PASSWORD no servidor.
// Exemplo de DSN: pgsql:host=SEU_HOST;port=5432;dbname=SEU_BANCO
$secret=getenv('ALTHA_WEBHOOK_SECRET');
if (!$secret) { http_response_code(503); exit; }
$raw=file_get_contents('php://input');
$t=$_SERVER['HTTP_X_ALTHAONE_TIMESTAMP']??'';
$sig=$_SERVER['HTTP_X_ALTHAONE_SIGNATURE']??'';
if (!is_string($raw) || !preg_match('/^\d{1,12}$/D',$t) ||
abs(time()-(int)$t)>300 || !preg_match('/^sha256=[a-f0-9]{64}$/D',$sig)) {
http_response_code(401); exit;
}
$expected='sha256='.hash_hmac('sha256',$t.'.'.$raw,$secret);
if (!hash_equals($expected,$sig)) { http_response_code(401); exit; }
try { $event=json_decode($raw,true,512,JSON_THROW_ON_ERROR); }
catch (Throwable $e) { http_response_code(400); exit; }
if (!is_array($event) || !is_string($event['id']??null) ||
!str_starts_with($event['id'],'evt_') ||
$event['id']!==($_SERVER['HTTP_X_ALTHAONE_EVENT_ID']??'') ||
($event['api_version']??null)!=='1' ||
!is_string($event['type']??null) || !is_array($event['data']??null)) {
http_response_code(400); exit;
}
$pdo=null;
try {
$pdo=new PDO(getenv('PDO_DSN'),getenv('PDO_USER'),getenv('PDO_PASSWORD'),[
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
]);
$pdo->beginTransaction();
$q=$pdo->prepare('INSERT INTO integration_received_events(id,event_type) VALUES(?,?) ON CONFLICT DO NOTHING');
$q->execute([$event['id'],$event['type']]);
if (!$q->rowCount()) { $pdo->commit(); http_response_code(204); exit; }
$d=$event['data'];
if ($event['type']==='pix.charge.paid') {
if (($d['status']??null)!=='PAID' || ($d['currency']??null)!=='BRL' ||
!is_int($d['amount_minor']??null)) throw new RuntimeException('Pagamento incompatível');
$q=$pdo->prepare('SELECT * FROM integration_orders WHERE external_reference=? FOR UPDATE');
$q->execute([$d['external_reference']]);
$o=$q->fetch(PDO::FETCH_ASSOC);
if (!$o || (int)$o['amount_minor']!==$d['amount_minor'] ||
$o['currency']!==$d['currency'] ||
($o['altha_charge_id'] && $o['altha_charge_id']!==$d['id']))
throw new RuntimeException('Cobrança não corresponde ao pedido');
$q=$pdo->prepare('UPDATE integration_orders SET altha_charge_id=?,paid_at=COALESCE(paid_at,now()) WHERE id=?');
$q->execute([$d['id'],$o['id']]);
// Grave aqui uma outbox local se houver entrega de produto posterior.
}
if ($event['type']==='pix.withdrawal.completed' && ($d['source']??null)==='AUTOMATIC_PAYOUT') {
if (($d['status']??null)!=='COMPLETED' || empty($d['source_charge_id']))
throw new RuntimeException('Repasse incompatível');
$q=$pdo->prepare('SELECT * FROM integration_orders WHERE external_reference=? FOR UPDATE');
$q->execute([$d['external_reference']]);
$o=$q->fetch(PDO::FETCH_ASSOC);
if (!$o || ($o['altha_charge_id'] && $o['altha_charge_id']!==$d['source_charge_id']))
throw new RuntimeException('Repasse não corresponde ao pedido');
$q=$pdo->prepare('UPDATE integration_orders SET altha_charge_id=?,payout_completed_at=COALESCE(payout_completed_at,now()) WHERE id=?');
$q->execute([$d['source_charge_id'],$o['id']]);
// Não marque paid_at com um evento de saque.
}
$pdo->commit();
http_response_code(204);
} catch (Throwable $e) {
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
// Logue ID e diagnóstico sanitizado; não devolva stack trace nem segredo.
http_response_code(503);
}Para um processamento com inbox própria, substitua a atualização síncrona por gravação durável do evento e processamento posterior. Mantenha a assinatura sobre o corpo bruto e uma chave única do ID em qualquer arquitetura escolhida.
21. Erros e resolução de problemas
Erro HTTP e erro no recurso
Há duas situações distintas. Uma requisição rejeitada pelo endpoint retorna HTTP de erro e envelope error com code, message e request_id. Uma intenção financeira aceita pode retornar HTTP 201/200 com seu próprio status e error: {code,message}. Nesse segundo caso, o ID da operação continua válido.
Envelope ilustrativo de erro HTTP
json
{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "A chave de idempotência já foi usada com outros dados.",
"request_id": "identificador-para-suporte"
}
}| Código HTTP de erro | HTTP usual | Como resolver |
|---|---|---|
| INVALID_API_KEY | 401 | Revisar Bearer, ambiente e revogação. Gere outra credencial pelo painel se a original foi perdida. |
| PERMISSION_DENIED | 403 | Selecionar a permissão necessária em uma credencial adequada. |
| SELLER_PIX_UNAVAILABLE | 403 | Revisar estado ativo e capacidade Pix do cadastro existente. |
| RESOURCE_NOT_FOUND | 404 | Conferir o ID e a credencial do lojista. Recursos de outro lojista também aparecem como inexistentes. |
| IDEMPOTENCY_CONFLICT | 409 | Recuperar o corpo original. Não alterar conteúdo de uma intenção já persistida. |
| IDEMPOTENCY_KEY_REQUIRED | 400 | Enviar chave de 1–128 caracteres no formato permitido; o código também cobre formato inválido do header. |
| INVALID_DATA | 400 | Conferir nomes, tipos, campos extras, centavos inteiros e limites do corpo. |
| INVALID_PAGINATION | 400 | Usar page de 1–10000 e limit de 1–100, ambos inteiros. |
| INVALID_DATE | 400 | Enviar from/to com datas válidas; preferir ISO UTC com Z. |
| INVALID_STATUS | 400 | Usar um estado da modalidade consultada. PAID não é estado de saque. |
| WEBHOOK_DESTINATION_BLOCKED | 400 no painel | Conferir HTTPS, porta, DNS IPv4 público, ausência de credenciais/fragmento e redirecionamento. |
| RATE_LIMITED | 429 | Reduzir concorrência e polling. Aplicar espera progressiva com variação aleatória e respeitar os headers disponíveis. |
| OPERATION_CONFLICT | 409 | Verificar o estado e a configuração da intenção antes de repetir. |
| PROVIDER_UNAVAILABLE | 503 | Preservar a intenção e consultar/repetir com a mesma idempotência; incluir request_id ao suporte. |
| WALLET_NOT_FOUND | 404 quando aplicável | Revisar o vínculo da carteira no cadastro existente. |
| Código dentro do recurso | Significado | Ação |
|---|---|---|
| PROCESSING_PENDING | Processamento/condição pendente ou resultado ainda não resolvido. | Consultar o mesmo ID; não disparar outra intenção financeira. |
| INSUFFICIENT_BALANCE | Disponível insuficiente para a saída e sua tarifa, ou condição de lastro equivalente. | Verificar saldo reservado, total_debit_minor e operações existentes. |
| DESTINATION_UNAVAILABLE | Destino ausente, indisponível ou sem condição válida de titularidade. | Corrigir o cadastro existente e acompanhar a mesma ordem. |
| LIMIT_REACHED | Limite efetivo do cadastro ou destino impede a preparação. | Ler o contexto e aguardar/corrigir conforme a configuração; não trocar titular para contornar. |
| PRICING_CHANGED | A tarifa mudou antes da preparação da intenção. | Condições originais foram preservadas. Consultar suporte com o ID; não aplicar nova tarifa localmente. |
| INVALID_DATA | Falha local definitiva de valor ou dados antes de criar a operação externa. | Inspecionar os dados e a tarifa. O recurso pode estar FAILED; não esperar retry automático indefinido. |
| CHARGE_FAILED | Cobrança recusada/falha definitiva no provedor. | Conferir a operação no painel e tratar o pedido sem considerá-lo pago. |
| WITHDRAWAL_FAILED | Repasse não concluído pelo provedor. | Verificar a falha definitiva e a situação financeira antes de nova ordem. |
O campo error do recurso não traz necessariamente request_id. Guarde o header X-Request-Id da chamada e o ID do recurso. O formato padronizado acima cobre os erros tratados pelo módulo; uma resposta HTML ou não JSON do proxy deve ser tratada como falha de transporte, sem presumir que uma operação não foi criada.
Roteiro rápido de diagnóstico
- Separe o HTTP recebido do
statusfinanceiro e deerror.code. - Confirme se o ID é cobrança ou saída e se pertence à credencial usada.
- Compare o valor com o pedido local em centavos.
- Consulte o ID sem criar outra intenção.
- Verifique se há repasse reservado, destino pendente ou entrega de webhook esgotada.
- Envie ao suporte somente identificadores e diagnóstico sanitizado.
22. OpenAPI e Postman
Baixe o OpenAPI 3.1 para consultar schemas de entrada e saída, autenticação e endpoints. A coleção Postman oferece sete requisições: criar/listar/consultar cobrança, criar/listar/consultar saque e consultar saldo.
- Importe o JSON da coleção no Postman.
- Confira
base_url:https://api.althaone.com.br/api/v1/pix. - Preencha
api_keyem ambiente privado/local. Não publique a variável com valor real. - Comece por Saldo ou por uma listagem, que não criam movimentação.
- Para uma cobrança real autorizada, ajuste
charges_idempotency_keye o corpo; depois salve o ID emcharge_id. - Para uma saída real autorizada, ajuste
withdrawals_idempotency_keye o corpo; salve o ID emwithdrawal_id. - Em timeout, preserve a variável de idempotência. Para outra intenção financeira, crie e persista outra chave conscientemente.
As variáveis da coleção não são um banco de pedidos e não substituem a persistência do seu servidor. Não use execução automática de toda a coleção em produção, pois ela inclui POSTs financeiros. A coleção não contém uma credencial válida por padrão.
A documentação, a API e os eventos usam a versão 1 do contrato. Os exemplos com IDs fictícios destinam-se à compreensão estrutural; consultas reais precisam dos identificadores retornados à sua própria credencial.
23. Operação, conciliação e suporte
Polling e limites
O limite do módulo é de 60 chamadas por minuto por origem, além dos controles globais existentes. Vários sistemas atrás do mesmo endereço de saída podem compartilhar o limite. Prefira webhooks e consultas pontuais. Se precisar de polling, distribua os intervalos e aumente a espera quando a operação continuar pendente.
Não consulte cada pedido a cada segundo em uma loja com muitos pedidos. Use um worker local com fila, intervalos progressivos e deduplicação de IDs. Para cobranças já pagas e saídas concluídas, interrompa o polling de rotina; mantenha apenas sua conciliação periódica.
Recuperação após indisponibilidade do seu sistema
- Restaure a conexão e confira se o receptor está respondendo 2xx após persistir os eventos.
- Consulte as cobranças e saques cujos IDs locais ainda estão pendentes.
- Use listagens por janelas de criação para procurar recursos sem vínculo local completo, deduplicando por ID.
- Compare referências, valores e moeda antes de atualizar o pedido.
- No painel, reenvie eventos com tentativas esgotadas quando o receptor estiver corrigido.
Não gere uma nova cobrança ou um novo saque para reconciliar uma operação existente. Conciliação recupera o estado da mesma obrigação financeira; não é um novo movimento.
Dados para suporte
Guarde o X-Request-Id das chamadas e o error.request_id quando houver envelope de erro. Para um atendimento útil, informe: método e caminho, horário UTC, ID da cobrança/saque, referência externa, HTTP, estado, código de erro e ID do evento de webhook, quando aplicável.
Não envie Bearer completo, segredo de webhook, Authorization, cookies, CPF/CNPJ completo, chave Pix integral ou corpo de requisição com dados pessoais. Prefixo da credencial e identificadores de recurso costumam bastar para localizar a ocorrência.
O histórico do painel e a consulta por ID complementam seus logs. Um evento entregue significa que seu receptor respondeu 2xx; não comprova por si só que a aplicação do lojista executou a regra de negócio esperada. Sua aplicação deve persistir e monitorar esse processamento.
24. Perguntas frequentes e checklist
Quais APIs meu servidor precisa integrar?
Seu servidor chama exclusivamente a API Altha One e recebe as notificações Altha One. Todas as operações necessárias estão descritas neste portal.
Posso usar a chave de API no navegador?
Não. Use seu backend como intermediário. O navegador recebe QR Code, copia e cola e o estado do pedido, sem a credencial.
Posso informar outra chave Pix no saque?
Não. A saída usa um destino cadastrado e habilitado do próprio lojista. A API rejeita um campo de chave livre no corpo.
Recebi 201. Posso liberar o pedido?
Não por esse motivo. Confira a confirmação PAID, preferencialmente no webhook validado e correlacionado com seu pedido. O 201 registra a intenção.
O pagamento está PAID e o disponível está zerado. Há erro?
Não necessariamente. O líquido da cobrança pode estar reservado para saída automática. Consulte reserved_minor e o repasse vinculado.
O saque tem end_to_end_id. Já foi concluído?
Não necessariamente. A conclusão financeira é indicada por status=COMPLETED, não apenas pelo identificador.
Recebi o mesmo webhook duas vezes. Devo atualizar duas vezes?
Não. Deduplicate event.id de forma persistente e idempotente. Pode ter ocorrido falha depois de o receptor gravar a primeira entrega e antes de responder.
Minha URL retorna 301 para o caminho correto. Funciona?
Não como entrega aceita. Cadastre a URL HTTPS final que aceita o POST diretamente, sem depender de redirects.
Posso usar external_reference como idempotência?
Você pode usar um valor parecido como chave, desde que preserve a intenção, mas os campos têm papéis diferentes. Somente o header Idempotency-Key participa da deduplicação financeira documentada.
Como trato uma intenção em RECONCILING?
Mantenha seu ID, valor e referência. Consulte o estado e aguarde a conciliação da mesma operação. Não solicite outra saída com outra chave para tentar acelerar.
Revogar a credencial cancela uma venda ou saque já criado?
Não. Revogação bloqueia chamadas futuras; operações persistidas e obrigações financeiras continuam seu fluxo.
O webhook de teste valida um pagamento ou uma saída real?
Não. Ele valida seu receptor, a assinatura e a entrega de um evento sintético.
Checklist do integrador
- Credencial guardada exclusivamente no backend, com permissões necessárias.
- Valores calculados no servidor e enviados como centavos inteiros.
- Pedido e chave de idempotência persistidos antes do POST.
- ID Altha One guardado sem substituir outro vínculo silenciosamente.
- Tratamento explícito de 201 com processamento pendente, timeout, 409 e 429.
- Pagamento e repasse em campos locais independentes.
- Webhook HTTPS público sem redirect, corpo bruto e HMAC em tempo constante.
- Relógio sincronizado, ID do header conferido e evento deduplicado no banco.
- Valor, moeda e identidade da cobrança comparados antes de liberar o pedido.
- Retentativas e eventos fora de ordem tratados sem regressão de fatos confirmados.
- Histórico e conciliação de operações com resultado incerto.
- Logs com request_id e IDs de recurso, sem segredos ou dados pessoais desnecessários.
Campos e regras deste guia correspondem ao módulo publicado. Valores de exemplo não definem preço comercial nem garantem liquidação em prazo fixo. Leia os valores efetivos retornados para cada operação do lojista.
Criar cobrança
/chargesRegistra a intenção de cobrança e devolve os dados do Pix quando disponíveis. Requer Idempotency-Key. HTTP 201 não confirma pagamento.
Permissão da credencial: charges:write.
Como enviar a requisição
Headers obrigatórios: Authorization: Bearer e Idempotency-Key. Content-Type: application/json. Corpo diretamente em JSON, sem envelope data/payload.
| Campo | Tipo | Obrigatório | Regra |
|---|---|---|---|
| amount_minor | integer | Sim | Mínimo 1. Máximo 2000000000 |
| external_reference | string | Sim | Até 128 caracteres |
| description | string | Não | Até 200 caracteres. Padrão: "" |
| expires_in_seconds | integer | Não | Mínimo 60. Máximo 86400. Padrão: 3600 |
| metadata | object | Não | Até 4096 bytes em JSON. |
Resposta 201
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| id | string | Conforme representação do recurso. |
| external_reference | string | Conforme representação do recurso. |
| status | enum | Valores: PENDING, PAID, EXPIRED, CANCELLED, FAILED |
| amount_minor | integer | Mínimo 0 |
| currency | const BRL | Conforme representação do recurso. |
| created_at | string | null | Conforme representação do recurso. |
| updated_at | string | null | Conforme representação do recurso. |
| source | enum | Valores: API, AUTOMATIC_PAYOUT |
| source_charge_id | string | null | Conforme representação do recurso. |
| error | object | null | Conforme representação do recurso. |
| qr_copy_paste | string | Conforme representação do recurso. |
| qr_image | string | null | Data URL PNG pronta para img src. |
| expires_at | string | null | Conforme representação do recurso. |
| paid_at | string | null | Conforme representação do recurso. |
| automatic_payout | const true | Conforme representação do recurso. |
| withdrawal | object | null | Conforme representação do recurso. |
| withdrawal.id | string | Conforme representação do recurso. |
| withdrawal.status | string | Conforme representação do recurso. |
| commercial | object | Conforme representação do recurso. |
| commercial.gross_minor | integer | Mínimo 0 |
| commercial.fee_minor | integer | null | Mínimo 0 |
| commercial.net_minor | integer | null | Mínimo 0 |
| commercial.applied | boolean | Conforme representação do recurso. |
| metadata | object | Conforme representação do recurso. |
Resposta de referência
{
"id": "11111111-1111-4111-8111-111111111111",
"external_reference": "pedido-123",
"status": "PENDING",
"amount_minor": 10000,
"currency": "BRL",
"source": "API",
"source_charge_id": null,
"created_at": "2026-11-01T12:00:00.000Z",
"updated_at": "2026-11-01T12:00:00.000Z",
"expires_at": "2026-11-01T13:00:00.000Z",
"paid_at": null,
"qr_copy_paste": "CONTEUDO_ILUSTRATIVO_NAO_PAGAVEL",
"qr_image": null,
"automatic_payout": true,
"withdrawal": null,
"commercial": {
"gross_minor": 10000,
"fee_minor": 150,
"net_minor": 9850,
"applied": false
},
"metadata": {
"order_id": "123"
},
"error": null
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Listar cobranças
/chargesLista somente cobranças desta integração do lojista autenticado, com filtros e paginação.
Permissão da credencial: charges:read.
Como enviar a requisição
Header obrigatório: Authorization: Bearer. A consulta não cria uma nova intenção financeira.
| Parâmetro | Onde | Formato |
|---|---|---|
| from | query | {"type":"string"} |
| to | query | {"type":"string"} |
| status | query | {"type":"string"} |
| external_reference | query | {"type":"string"} |
| page | query | {"type":"integer","default":1,"minimum":1,"maximum":10000} |
| limit | query | {"type":"integer","default":20,"minimum":1,"maximum":100} |
Resposta 200
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| items | array | Conforme representação do recurso. |
| page | integer | Mínimo 0 |
| limit | integer | Mínimo 0 |
| total | integer | Mínimo 0 |
Resposta de referência
{
"items": [
{
"id": "11111111-1111-4111-8111-111111111111",
"external_reference": "pedido-123",
"status": "PENDING",
"amount_minor": 10000,
"currency": "BRL",
"source": "API",
"source_charge_id": null,
"created_at": "2026-11-01T12:00:00.000Z",
"updated_at": "2026-11-01T12:00:00.000Z",
"expires_at": "2026-11-01T13:00:00.000Z",
"paid_at": null,
"qr_copy_paste": "CONTEUDO_ILUSTRATIVO_NAO_PAGAVEL",
"qr_image": null,
"automatic_payout": true,
"withdrawal": null,
"commercial": {
"gross_minor": 10000,
"fee_minor": 150,
"net_minor": 9850,
"applied": false
},
"metadata": {
"order_id": "123"
},
"error": null
}
],
"page": 1,
"limit": 20,
"total": 1
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Consultar cobrança
/charges/{id}Recupera o estado atual pelo identificador Altha One. IDs de outro lojista retornam recurso inexistente.
Permissão da credencial: charges:read.
Como enviar a requisição
Header obrigatório: Authorization: Bearer. A consulta não cria uma nova intenção financeira.
| Parâmetro | Onde | Formato |
|---|---|---|
| id | path | {"type":"string"} |
Resposta 200
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| id | string | Conforme representação do recurso. |
| external_reference | string | Conforme representação do recurso. |
| status | enum | Valores: PENDING, PAID, EXPIRED, CANCELLED, FAILED |
| amount_minor | integer | Mínimo 0 |
| currency | const BRL | Conforme representação do recurso. |
| created_at | string | null | Conforme representação do recurso. |
| updated_at | string | null | Conforme representação do recurso. |
| source | enum | Valores: API, AUTOMATIC_PAYOUT |
| source_charge_id | string | null | Conforme representação do recurso. |
| error | object | null | Conforme representação do recurso. |
| qr_copy_paste | string | Conforme representação do recurso. |
| qr_image | string | null | Data URL PNG pronta para img src. |
| expires_at | string | null | Conforme representação do recurso. |
| paid_at | string | null | Conforme representação do recurso. |
| automatic_payout | const true | Conforme representação do recurso. |
| withdrawal | object | null | Conforme representação do recurso. |
| withdrawal.id | string | Conforme representação do recurso. |
| withdrawal.status | string | Conforme representação do recurso. |
| commercial | object | Conforme representação do recurso. |
| commercial.gross_minor | integer | Mínimo 0 |
| commercial.fee_minor | integer | null | Mínimo 0 |
| commercial.net_minor | integer | null | Mínimo 0 |
| commercial.applied | boolean | Conforme representação do recurso. |
| metadata | object | Conforme representação do recurso. |
Resposta de referência
{
"id": "11111111-1111-4111-8111-111111111111",
"external_reference": "pedido-123",
"status": "PENDING",
"amount_minor": 10000,
"currency": "BRL",
"source": "API",
"source_charge_id": null,
"created_at": "2026-11-01T12:00:00.000Z",
"updated_at": "2026-11-01T12:00:00.000Z",
"expires_at": "2026-11-01T13:00:00.000Z",
"paid_at": null,
"qr_copy_paste": "CONTEUDO_ILUSTRATIVO_NAO_PAGAVEL",
"qr_image": null,
"automatic_payout": true,
"withdrawal": null,
"commercial": {
"gross_minor": 10000,
"fee_minor": 150,
"net_minor": 9850,
"applied": false
},
"metadata": {
"order_id": "123"
},
"error": null
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Solicitar saque
/withdrawalsO valor solicitado é destinado ao recebedor. A tarifa é adicional no débito da carteira. A chave vem do cadastro existente.
Permissão da credencial: withdrawals:write.
Como enviar a requisição
Headers obrigatórios: Authorization: Bearer e Idempotency-Key. Content-Type: application/json. Corpo diretamente em JSON, sem envelope data/payload.
| Campo | Tipo | Obrigatório | Regra |
|---|---|---|---|
| amount_minor | integer | Sim | Mínimo 1. Máximo 2000000000 |
| external_reference | string | Sim | Até 128 caracteres |
Resposta 201
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| id | string | Conforme representação do recurso. |
| external_reference | string | Conforme representação do recurso. |
| status | enum | Valores: QUEUED, PROCESSING, RECONCILING, COMPLETED, FAILED |
| amount_minor | integer | Mínimo 0 |
| currency | const BRL | Conforme representação do recurso. |
| created_at | string | null | Conforme representação do recurso. |
| updated_at | string | null | Conforme representação do recurso. |
| source | enum | Valores: API, AUTOMATIC_PAYOUT |
| source_charge_id | string | null | Conforme representação do recurso. |
| error | object | null | Conforme representação do recurso. |
| fee_minor | integer | Mínimo 0 |
| total_debit_minor | integer | Mínimo 0 |
| destination | object | null | Conforme representação do recurso. |
| completed_at | string | null | Conforme representação do recurso. |
| end_to_end_id | string | null | Nulo quando não disponibilizado pela evidência persistida. |
Resposta de referência
{
"id": "22222222-2222-4222-8222-222222222222",
"external_reference": "saque-123",
"status": "QUEUED",
"source": "API",
"source_charge_id": null,
"amount_minor": 5000,
"fee_minor": 80,
"total_debit_minor": 5080,
"currency": "BRL",
"created_at": "2026-11-01T12:00:00.000Z",
"updated_at": "2026-11-01T12:00:00.000Z",
"destination": null,
"completed_at": null,
"end_to_end_id": null,
"error": null
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Listar saques
/withdrawalsInclui saques da API e repasses automáticos destas cobranças, mantendo pagamento e saída separados.
Permissão da credencial: withdrawals:read.
Como enviar a requisição
Header obrigatório: Authorization: Bearer. A consulta não cria uma nova intenção financeira.
| Parâmetro | Onde | Formato |
|---|---|---|
| from | query | {"type":"string"} |
| to | query | {"type":"string"} |
| status | query | {"type":"string"} |
| external_reference | query | {"type":"string"} |
| page | query | {"type":"integer","default":1,"minimum":1,"maximum":10000} |
| limit | query | {"type":"integer","default":20,"minimum":1,"maximum":100} |
Resposta 200
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| items | array | Conforme representação do recurso. |
| page | integer | Mínimo 0 |
| limit | integer | Mínimo 0 |
| total | integer | Mínimo 0 |
Resposta de referência
{
"items": [
{
"id": "22222222-2222-4222-8222-222222222222",
"external_reference": "saque-123",
"status": "QUEUED",
"source": "API",
"source_charge_id": null,
"amount_minor": 5000,
"fee_minor": 80,
"total_debit_minor": 5080,
"currency": "BRL",
"created_at": "2026-11-01T12:00:00.000Z",
"updated_at": "2026-11-01T12:00:00.000Z",
"destination": null,
"completed_at": null,
"end_to_end_id": null,
"error": null
}
],
"page": 1,
"limit": 20,
"total": 1
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Consultar saque
/withdrawals/{id}Acompanhe a mesma intenção até o resultado final. Rastreio ou aceitação não substitui COMPLETED.
Permissão da credencial: withdrawals:read.
Como enviar a requisição
Header obrigatório: Authorization: Bearer. A consulta não cria uma nova intenção financeira.
| Parâmetro | Onde | Formato |
|---|---|---|
| id | path | {"type":"string"} |
Resposta 200
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| id | string | Conforme representação do recurso. |
| external_reference | string | Conforme representação do recurso. |
| status | enum | Valores: QUEUED, PROCESSING, RECONCILING, COMPLETED, FAILED |
| amount_minor | integer | Mínimo 0 |
| currency | const BRL | Conforme representação do recurso. |
| created_at | string | null | Conforme representação do recurso. |
| updated_at | string | null | Conforme representação do recurso. |
| source | enum | Valores: API, AUTOMATIC_PAYOUT |
| source_charge_id | string | null | Conforme representação do recurso. |
| error | object | null | Conforme representação do recurso. |
| fee_minor | integer | Mínimo 0 |
| total_debit_minor | integer | Mínimo 0 |
| destination | object | null | Conforme representação do recurso. |
| completed_at | string | null | Conforme representação do recurso. |
| end_to_end_id | string | null | Nulo quando não disponibilizado pela evidência persistida. |
Resposta de referência
{
"id": "22222222-2222-4222-8222-222222222222",
"external_reference": "saque-123",
"status": "QUEUED",
"source": "API",
"source_charge_id": null,
"amount_minor": 5000,
"fee_minor": 80,
"total_debit_minor": 5080,
"currency": "BRL",
"created_at": "2026-11-01T12:00:00.000Z",
"updated_at": "2026-11-01T12:00:00.000Z",
"destination": null,
"completed_at": null,
"end_to_end_id": null,
"error": null
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Consultar saldo
/balanceConsulta a carteira interna do lojista. Retorna somente os valores da carteira autenticada.
Permissão da credencial: balance:read.
Como enviar a requisição
Header obrigatório: Authorization: Bearer. A consulta não cria uma nova intenção financeira.
| Parâmetro | Onde | Formato |
|---|
Resposta 200
Os valores abaixo são ilustrativos. As tarifas e os limites reais são definidos no cadastro do lojista; null representa informação ainda não disponível.
| Campo retornado | Tipo | Interpretação |
|---|---|---|
| currency | const BRL | Conforme representação do recurso. |
| available_minor | integer | Mínimo 0 |
| reserved_minor | integer | Mínimo 0 |
| contractual_reserve_minor | integer | Mínimo 0 |
| other_pending_minor | integer | Mínimo 0 |
| updated_at | string | null | Conforme representação do recurso. |
Resposta de referência
{
"available_minor": 20000,
"reserved_minor": 0,
"contractual_reserve_minor": 0,
"other_pending_minor": 0,
"currency": "BRL",
"updated_at": "2026-11-01T12:00:00.000Z"
}Erros que sua integração deve tratar
| Situação | Tratamento |
|---|---|
| 401 / 403 | Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro. |
| 404 | Confira o ID, a modalidade e o lojista da credencial. |
| 409 | Examine o código: conflito de idempotência ou condição da operação não são aprovação. |
| 429 | Reduza a frequência e use espera progressiva. |
| 503 / timeout | Preserve a intenção. Consulte pelo ID ou repita o mesmo POST com a mesma chave e corpo. |
Em uma intenção aceita, error pode informar processamento pendente mesmo com HTTP 201/200. Consulte o catálogo de erros e registre X-Request-Id.
Playground: execute um fluxo Pix
Experimente o contrato com requisições HTTP reais a um simulador da documentação. As chamadas usam somente dados fictícios, separados das carteiras e do financeiro de produção. Valores, beneficiários, tarifas e eventos são fictícios.
Simulação: https://docs.althaone.com.br/sandbox/v1/pix. Produção: https://api.althaone.com.br/api/v1/pix. Os endpoints /sandbox/session e /sandbox/scenarios existem somente no laboratório.
O que a simulação comprova
Ela permite praticar os formatos, a correlação de IDs, a repetição idempotente, os estados e a validação de assinatura. Não comprova habilitação comercial, titularidade real, limites reais, disponibilidade do provedor ou liquidação bancária.
Estado temporário
Cada sessão é isolada e fica disponível por até 30 minutos de inatividade. Reinícios podem apagar os dados fictícios. A carteira começa com R$ 200,00 fictícios; as tarifas didáticas são 1,50% de entrada e R$ 0,80 de saída. Esses valores não representam a tabela comercial do lojista.
O laboratório aceita apenas tokens demo_ criados nele. Nunca informe uma chave LIVE aqui. No contrato LIVE, continue guardando a chave no seu próprio backend.
Exemplos em oito linguagens
Escolha a operação e a linguagem para obter um arquivo completo de requisição. Os exemplos usam HTTP padrão e variáveis de ambiente, sem SDK fictício. O modo LIVE gera código para o seu servidor; o portal não recebe sua credencial nem executa operações financeiras LIVE.
| Linguagem | Requisitos do exemplo |
|---|---|
| cURL | cURL moderno e shell; chave no ambiente |
| Node.js | Node.js 20+; fetch nativo |
| PHP | PHP 8.1+ com cURL |
| Python | Python 3.10+; urllib padrão |
| Go | Go 1.20+; net/http padrão |
| Java | Java 17+; java.net.http |
| C# | .NET 8+; HttpClient |
| Ruby | Ruby 3+; net/http padrão |
Antes de executar no seu servidor
- Selecione o ambiente correto e confira a URL.
- Defina ALTHA_API_KEY apenas para LIVE ou ALTHA_DEMO_TOKEN para a simulação.
- Em POST, carregue ALTHA_IDEMPOTENCY_KEY do registro já persistido do seu pedido.
- Leia HTTP, status e error separadamente.
- Em timeout, preserve o identificador e a chave; não implemente retry com outra intenção.
Baixar o pacote de exemplos. Os exemplos completos de receptores Node.js e PHP estão nos capítulos próprios. O receptor Python está no tópico seguinte.
Webhook em Python com deduplicação
Referência com biblioteca padrão Python e SQLite do seu próprio sistema. O endpoint lê o corpo bruto, confere a janela temporal, compara HMAC em tempo constante e grava o ID do evento no mesmo commit da atualização do pedido. Não é um endpoint da Altha One e não movimenta dinheiro.
Instale o exemplo atrás do HTTPS do seu servidor. O pedido e sua referência devem existir no banco local antes da chamada de criação. Adapte as tabelas à sua aplicação e use uma outbox local para entregas de produto posteriores.
webhook.py — receptor completo
# Python 3.10+ · salve como webhook.py e use HTTPS no proxy do seu sistema.
import os, json, time, hmac, hashlib, sqlite3
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
SECRET = os.environ["ALTHA_WEBHOOK_SECRET"].encode()
DB = os.environ.get("INTEGRATION_DB", "app-integracao.db")
with sqlite3.connect(DB) as db:
db.execute("CREATE TABLE IF NOT EXISTS received_events(id TEXT PRIMARY KEY)")
db.execute("""CREATE TABLE IF NOT EXISTS orders(
external_reference TEXT PRIMARY KEY, amount_minor INTEGER NOT NULL,
currency TEXT NOT NULL DEFAULT 'BRL', altha_charge_id TEXT UNIQUE,
paid_at TEXT, payout_completed_at TEXT)""")
# O seu checkout deve gravar os pedidos reais ANTES de criar a cobrança.
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def respond(self, status):
self.send_response(status)
self.end_headers()
def do_POST(self):
if self.path != "/webhooks/altha":
return self.respond(404)
try:
length = int(self.headers.get("Content-Length", "0"))
if length < 1 or length > 1048576:
return self.respond(413)
raw = self.rfile.read(length)
timestamp = self.headers.get("X-AlthaOne-Timestamp", "")
signature = self.headers.get("X-AlthaOne-Signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return self.respond(401)
expected = "sha256=" + hmac.new(
SECRET, timestamp.encode() + b"." + raw, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
return self.respond(401)
event = json.loads(raw)
if event.get("id") != self.headers.get("X-AlthaOne-Event-Id"):
return self.respond(400)
if event.get("api_version") != "1":
return self.respond(400)
except (ValueError, TypeError, AttributeError):
return self.respond(400)
db = sqlite3.connect(DB, timeout=5, isolation_level=None)
try:
db.execute("BEGIN IMMEDIATE")
inserted = db.execute(
"INSERT OR IGNORE INTO received_events(id) VALUES(?)", (event["id"],)
).rowcount
if not inserted:
db.commit()
return self.respond(204)
d = event["data"]
if event["type"] == "pix.charge.paid":
row = db.execute(
"SELECT amount_minor,currency,altha_charge_id FROM orders WHERE external_reference=?",
(d["external_reference"],)
).fetchone()
if (not row or d.get("status") != "PAID"
or type(d.get("amount_minor")) is not int
or row[0] != d["amount_minor"] or row[1] != d["currency"]
or (row[2] and row[2] != d["id"])):
raise ValueError("Cobrança incompatível com o pedido")
db.execute(
"UPDATE orders SET altha_charge_id=?,paid_at=COALESCE(paid_at,datetime('now')) WHERE external_reference=?",
(d["id"], d["external_reference"])
)
# Grave uma outbox local aqui para entrega posterior do produto.
if event["type"] == "pix.withdrawal.completed" and d.get("source") == "AUTOMATIC_PAYOUT":
row = db.execute(
"SELECT altha_charge_id FROM orders WHERE external_reference=?",
(d["external_reference"],)
).fetchone()
if (not row or d.get("status") != "COMPLETED"
or not d.get("source_charge_id")
or (row[0] and row[0] != d["source_charge_id"])):
raise ValueError("Repasse incompatível")
db.execute(
"UPDATE orders SET altha_charge_id=?,payout_completed_at=COALESCE(payout_completed_at,datetime('now')) WHERE external_reference=?",
(d["source_charge_id"], d["external_reference"])
)
# Não altere paid_at com um evento de repasse.
db.commit()
return self.respond(204)
except Exception:
db.rollback()
return self.respond(503)
finally:
db.close()
if __name__ == "__main__":
ThreadingHTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
Comportamentos de recuperação
Um evento repetido devolve 204 sem repetir a atualização. Assinatura inválida devolve 401. Referência, ID, valor ou moeda divergente provocam rollback e resposta de erro; investigue antes de liberar o pedido. Um evento de repasse não preenche a confirmação de pagamento.
Veja o laboratório de assinatura para obter envelope, corpo bruto e headers de um evento fictício. Nunca valide uma assinatura após reserializar o JSON.
Postman: do primeiro Send ao Runner
São duas coleções com finalidades separadas. A coleção de demonstração executa o fluxo inteiro no laboratório; a LIVE documenta a API real e começa com credenciais vazias e escrita desabilitada no script de preparação.
Roteiro da demonstração
- Importe a coleção Demonstração e execute Iniciar sessão. O token fictício e o segredo de webhook são capturados em variáveis da coleção.
- Crie uma cobrança; o ID é guardado automaticamente. Repita a mesma solicitação para observar o mesmo ID.
- Execute o caso com corpo diferente para conferir o 409.
- Simule o pagamento e veja a intenção automática de saída, sem duplicar o saldo.
- Marque a saída em conciliação e depois conclua o cenário fictício.
- Consulte os eventos assinados e os resultados dos testes.
- No Collection Runner, execute a coleção completa na ordem disponibilizada. Ela não chama a API LIVE.
Variáveis e testes
| Variável | Uso |
|---|---|
| demo_root | URL fixa do laboratório; não substitua pela API LIVE. |
| demo_token / demo_webhook_secret | Credenciais exclusivamente fictícias capturadas na sessão. |
| charge_id / withdrawal_id | IDs da sessão, capturados nas respostas. |
| run_id | Correlaciona a execução e mantém chaves estáveis dentro dela. |
| allow_live_writes | A coleção LIVE mantém false até uma decisão financeira explícita de quem opera seu servidor. |
| api_key | Somente na coleção LIVE; preenchida privadamente no Postman pelo próprio lojista. Nunca forneça essa chave ao portal. |
Os testes conferem código HTTP, moeda, valores inteiros, conservação de IDs, estado e ausência de crédito duplicado. A validação criptográfica usa Web Crypto no Postman atual. Não compartilhe um ambiente exportado com credenciais reais.
Importar, atualizar e compartilhar
Você pode importar por URL ou arquivo. Importe novamente para obter uma versão nova; uma cópia importada não se atualiza sozinha. As URLs de importação deste portal são públicas e podem ser usadas em uma conta individual do Postman, sem equipe ou convite. Importe a coleção e o ambiente correspondente.
Referências oficiais: Importação de coleções, Run in Postman e Controle da execução.
Receitas de integração para casos reais
Loja virtual e ERP
- O ERP persiste o pedido, o preço e a chave de idempotência.
- Seu backend cria a cobrança e devolve os campos públicos ao checkout.
- O receptor autenticado grava o evento e marca o pedido pago com validação de valor/moeda.
- Uma outbox local libera o produto uma única vez.
- O repasse é acompanhado em outra coluna, sem desfazer o pedido pago.
Cobranças criadas simultaneamente
Use uma chave estável por intenção, não por tentativa HTTP. Duas chamadas concorrentes do mesmo pedido compartilham a chave e o corpo. Guarde o primeiro ID confirmado e rejeite vínculos locais incompatíveis. external_reference ajuda a encontrar o pedido, mas não é sua trava financeira.
Webhook chegou antes da resposta do checkout
O pedido e sua referência já devem existir localmente. Um receptor pode estabelecer o vínculo com data.id quando a referência exata, o valor e a moeda coincidirem e não existir outro ID vinculado. Depois, a gravação da resposta deve respeitar esse vínculo. Veja os exemplos completos Node.js, PHP e Python.
O lojista vê pagamento, mas ainda não recebeu repasse
Mostre o pagamento como PAID e o saque em sua situação própria. Leia reserved_minor e o erro do saque. Não crie uma saída manual da mesma obrigação para acelerar uma intenção em conciliação.
Seu receptor ficou indisponível
Recupere a infraestrutura, consulte IDs locais ainda pendentes e depois reenvie eventos pelo histórico. Use janelas de criação nas listagens, deduplique por ID e preserve fatos finais. Um replay de notificação não é um novo crédito ou saque.
O provedor respondeu de forma incerta
Persista o resultado como desconhecido, mantenha a mesma chave e acompanhe o ID. Reserve retries automáticos para o transporte da mesma intenção; nunca transforme timeout em autorização para uma nova transferência.
Integração para mais de um lojista
Guarde credenciais separadas em seu backend e escolha-as pelo lojista autenticado no seu próprio sistema. Não aceite Merchant ID livre para consultar a carteira de terceiros. Segregue IDs, eventos, filas e permissões locais. Uma credencial Altha One acessa somente os recursos vinculados a ela.
