Ir para o conteúdo
Altha OneDevelopers v1

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.

  1. Seu backend cria uma cobrança na Altha One e guarda o identificador retornado.
  2. Seu checkout exibe a imagem do QR Code e o código Pix copia e cola.
  3. O pagador realiza o pagamento no aplicativo bancário.
  4. A Altha One recebe a notificação do provedor, consulta a operação e confirma o recebimento financeiro.
  5. A cobrança passa a PAID. O líquido correspondente é reservado para uma única intenção de repasse automático.
  6. O repasse é processado para uma chave Pix cadastrada e habilitada, conforme seleção, tarifas e limites existentes.
  7. 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

  1. Entre no painel do próprio lojista e abra Pagamentos → Integração Pix.
  2. 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.
  3. Em Chaves de API, escolha um nome que identifique seu servidor e marque apenas as permissões necessárias.
  4. Clique em Gerar chave, copie o valor integral e guarde-o em um cofre ou variável de ambiente do seu backend.
  5. Em Webhooks, informe seu endpoint HTTPS, selecione os eventos e ative as entregas. Salve e guarde o segredo exibido.
  6. Prepare o receptor para validar a assinatura e persistir os eventos. Envie webhook.test pelo painel para conferir esse receptor sem operação financeira.
  7. 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ãoO que permiteQuando selecionar
charges:writePOST /chargesServidor que cria cobranças.
charges:readGET /charges e GET /charges/{id}Checkout, ERP ou rotina de conciliação.
withdrawals:writePOST /withdrawalsServidor autorizado a solicitar saque do saldo disponível. Não é necessária para o repasse automático das cobranças.
withdrawals:readGET /withdrawals e GET /withdrawals/{id}Acompanhamento de saques manuais e automáticos.
balance:readGET /balanceConsulta 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étodoCaminho relativo à basePermissãoResultado
POST/chargescharges:writeCria ou recupera a mesma intenção idempotente de cobrança.
GET/chargescharges:readLista cobranças desta integração pertencentes ao lojista.
GET/charges/{id}charges:readConsulta uma cobrança e o resumo do seu repasse.
POST/withdrawalswithdrawals:writeSolicita saque para um destino cadastrado; não aceita chave livre.
GET/withdrawalswithdrawals:readLista saques manuais da API e repasses automáticos desta integração.
GET/withdrawals/{id}withdrawals:readConsulta valor, tarifa, destino mascarado e situação.
GET/balancebalance:readConsulta 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.

CampoObrigatórioTipo e limitesComportamento
amount_minorSimNúmero inteiro entre 1 e 2.000.000.000Valor bruto da cobrança em centavos. Sujeito aos limites e custos reais.
external_referenceSimString de 1 a 128 caracteres, sem caracteres de controleIdentificador do pedido no seu sistema; preservado sem deduplicar sozinho.
descriptionNãoString com até 200 caracteres; padrão vazioMensagem do pedido, mapeada para payer_message quando enviada ao provedor.
expires_in_secondsNãoNúmero inteiro de 60 a 86400; padrão 3600Prazo solicitado em segundos. Use expires_at da resposta como vencimento registrado.
metadataNãoObjeto 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"
  }
}
CampoTipoInterpretação
idStringIdentificador Altha One. Use-o nas consultas e na correlação local.
external_referenceStringReferência enviada na criação.
statusStringPENDING, PAID, EXPIRED, CANCELLED ou FAILED.
amount_minor / currencyInteiro / BRLValor bruto da cobrança e moeda.
qr_copy_pasteStringPix copia e cola. Pode estar vazio enquanto a criação aguarda processamento.
qr_imageString data URL PNG ou nullImagem gerada localmente a partir do copia e cola. Pode ser usada diretamente em img.src.
created_at / updated_atData UTCCriação e última atualização da intenção local. updated_at não é comprovante de pagamento.
expires_atData UTC ou nullVencimento disponível na cobrança. Pode estar ausente antes do vínculo com o provedor.
paid_atData UTC ou nullInstante registrado da confirmação financeira.
source / source_charge_idAPI / nullCobranças criadas pelo endpoint têm origem API e não derivam de outra cobrança.
automatic_payoutBoolean trueO fluxo da nova cobrança prevê intenção automática de repasse; não garante conclusão da saída.
withdrawalObjeto {id,status} ou nullResumo do repasse vinculado, quando já existir. Consulte /withdrawals/{id} para detalhes.
commercial.gross_minorInteiroValor bruto considerado no cálculo comercial.
commercial.fee_minorInteiro ou nullTarifa comercial de recebimento do lojista, distinta do custo interno do provedor.
commercial.net_minorInteiro ou nullLíquido comercial da venda antes da tarifa de saída.
commercial.appliedBooleanfalse indica informação ainda estimada; true indica condição aplicada na cobrança confirmada.
metadataObjetoMetadados preservados da criação.
errorObjeto {code,message} ou nullPendê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âmetroPadrão / limiteRegra
page1; de 1 a 10000Número da página. Não é cursor.
limit20; de 1 a 100Quantidade máxima de itens por página.
fromOpcionalLimite inicial inclusivo da data de criação; envie ISO 8601 UTC com Z.
toOpcionalLimite final inclusivo da data de criação; envie ISO 8601 UTC com Z.
statusOpcionalEstado público válido da modalidade. O índice de estado acompanha o worker; a atualização pode ser assíncrona.
external_referenceOpcionalComparaçã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

  1. 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.
  2. 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.
  3. 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.
  4. Guarde o vínculo: salve o id Altha One no pedido e preserve o corpo original para eventual repetição.
  5. Exiba o Pix: devolva ao navegador QR Code, copia e cola, vencimento e estado; nunca a chave de API.
  6. 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.
  7. 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.
  8. Acompanhe o repasse: associe pix.withdrawal.completed ao pedido por data.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.

  1. A Altha One confirma o recebimento da operação vinculada à cobrança.
  2. O backend registra o crédito e os valores comerciais corretos.
  3. Na mesma transação de banco, cria uma única intenção de repasse ligada à cobrança e reserva o valor necessário.
  4. O worker prepara o destino cadastrado e a saída, preservando o snapshot do destino preparado.
  5. A saída é enviada e acompanhada. Uma resposta de aceitação do provedor não liquida o saque.
  6. Somente a consulta com estado financeiro COMPLETED permite 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çãoO que sua integração deve fazer
Destino indisponível ou sem chave elegívelManter 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 atingidaLer error.code/message, manter o vínculo e acompanhar o mesmo ID.
Resposta externa incertaAguardar conciliação da mesma operação, preservando a reserva.
Tarifa alterada antes da preparação da ordemPRICING_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álidaO pagamento continua confirmado; a intenção pode falhar com INVALID_DATA. Examine valor, tarifa e situação da carteira.
Falha definitiva da saídaTratar 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.

ConceitoCampo público / origemInterpretação
Bruto da vendacharge.amount_minor e commercial.gross_minorValor recebido na cobrança.
Tarifa comercial de entradacommercial.fee_minorPreço do recebimento cobrado do lojista, conforme sua configuração.
Líquido comercialcommercial.net_minorBruto menos tarifa comercial de entrada, antes da saída.
Reserva contratualbalance.contractual_reserve_minorRetenção contratual separada quando identificada; não é tarifa de aquisição nem reserva de saída.
Reserva para saquesbalance.reserved_minorValor comprometido com saídas em andamento, inclusive repasse automático.
Valor enviado ao recebedorwithdrawal.amount_minorQuantia destinada à chave Pix.
Tarifa comercial de saídawithdrawal.fee_minorPreço comercial do saque, separado do valor destinado.
Débito totalwithdrawal.total_debit_minorValor destinado mais tarifa comercial de saída.
Custos internos e margem Altha OneContabilidade internaNã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.

  1. Bruto: 10000 centavos.
  2. Tarifa de entrada: 150 centavos.
  3. Líquido comercial: 9850 centavos.
  4. Valor da saída automática destinado ao cliente: 9770 centavos.
  5. Tarifa de saída: 80 centavos.
  6. Débito total da carteira para esse repasse: 9850 centavos.

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"
}
CampoSignificado e uso
available_minorSaldo 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_minorValores comprometidos com saques e repasses em andamento. Não podem ser gastos simultaneamente por outra saída.
contractual_reserve_minorReserva 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_minorBucket de valores bloqueados preventivamente; não representa automaticamente o total bruto de cobranças pendentes de pagamento.
currencyBRL.
updated_atInstante 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 entradaObrigatórioFormatoRegra
amount_minorSimInteiro entre 1 e 2.000.000.000Valor que o destinatário deve receber, sujeito a saldo, tarifa e limites reais.
external_referenceSimString de 1 a 128 caracteres, sem controlesSua 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
}
CampoInterpretação
id / external_referenceIdentificador Altha One da saída e referência preservada.
statusQUEUED, PROCESSING, RECONCILING, COMPLETED ou FAILED.
sourceAPI para solicitação manual; AUTOMATIC_PAYOUT para saída gerada por uma cobrança desta integração.
source_charge_idID da cobrança de origem, ou null para saque manual. Use esse vínculo no webhook do repasse.
amount_minorValor destinado ao recebedor. Não desconte fee_minor novamente.
fee_minorTarifa comercial da saída.
total_debit_minoramount_minor + fee_minor.
destinationObjeto mascarado ou null enquanto a preparação não disponibilizou o destino.
destination.maskedKey / keyTypeChave mascarada e tipo da chave; nunca o segredo integral do destino.
destination.beneficiaryId / revisionIdentificação interna do beneficiário e revisão usada no snapshot. Trate como informação de acompanhamento.
destination.routeSelectionReason / authorizationSourceMotivo e fonte registrados pelo fluxo de seleção; podem variar conforme a configuração existente. Não são status financeiro.
created_at / updated_atDatas UTC da intenção. updated_at não comprova liquidação.
completed_atData UTC da conclusão financeira, ou null.
end_to_end_idRastreio Pix quando persistido; pode ser null e sua presença isolada não confirma liquidação.
errorCó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

EstadoO que significaAção do integrador
PENDINGCobrança em preparação ou aguardando confirmação financeira.Exibir QR quando disponível; aguardar confirmação ou consultar o ID.
PAIDRecebimento confirmado.Marcar o pedido pago uma única vez, conferindo identidade, valor e moeda. Acompanhar o repasse separadamente.
EXPIREDCobranç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.
CANCELLEDCancelamento representado pelo estado interno.Não considerar paga. Não há endpoint público de cancelamento nesta versão.
FAILEDFalha 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

EstadoO que significaAção do integrador
QUEUEDIntenção registrada, aguardando preparação ou condições elegíveis.Guardar o ID e ler error caso exista; não solicitar outra saída.
PROCESSINGSaída em processamento, ainda sem conclusão financeira.Acompanhar por webhook e consulta.
RECONCILINGResultado 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.
COMPLETEDConclusão financeira confirmada.Marcar repasse concluído uma única vez.
FAILEDFalha 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árioResultado esperado
Mesma chave e mesmo conteúdo normalizadoRetorna a mesma operação Altha One, podendo apresentar seu estado atualizado.
Mesma chave e outro valor, referência ou metadado relevanteHTTP 409 IDEMPOTENCY_CONFLICT; não cria outra operação.
Mesma referência externa e outra chavePode criar outra intenção. A referência externa não deduplica a requisição.
Duas chamadas simultâneas da mesma intençãoControle de concorrência e unicidade no banco preservam uma única intenção.
Conexão caiu antes da respostaResultado desconhecido para o cliente. Consulte o ID se já o conhece; caso contrário, repita com a chave e corpo originais.
Saque em RECONCILINGAcompanhe 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

  1. Marque localmente a tentativa como aguardando resultado, preservando o pedido original.
  2. Se recebeu um ID antes da falha, consulte-o.
  3. Se não recebeu ID, repita o mesmo POST com a mesma chave e o mesmo corpo.
  4. Se houver estado incerto ou conciliação, continue consultando essa operação com intervalos progressivos.
  5. 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:

CampoRegra
idID único do evento com prefixo evt_. Use-o para deduplicação durável.
typeTipo do evento da tabela abaixo.
created_atCriação do evento em UTC. Não é o timestamp da tentativa de entrega.
api_versionString "1".
dataFotografia do recurso no momento da criação do evento, ou dados sintéticos para webhook.test.
EventoConteúdo / interpretação
pix.charge.createdRecurso de cobrança registrado no fluxo. O QR pode ainda estar em preparação; confira data.status.
pix.charge.paidCobrança paga, com data.status=PAID e valores comerciais confirmados.
pix.charge.expiredCobrança identificada como expirada.
pix.charge.failedCobrança com falha definitiva registrada.
pix.withdrawal.processingIntenção de saída registrada/em acompanhamento. Confira data.status: o marco não garante que o provedor já iniciou o envio.
pix.withdrawal.completedSaída com conclusão financeira confirmada; source_charge_id identifica a cobrança automática de origem.
pix.withdrawal.failedSaída com falha definitiva registrada.
webhook.testObjeto 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

HeaderConteúdo
Content-Typeapplication/json
X-AlthaOne-Event-IdMesmo ID do campo id do envelope.
X-AlthaOne-TimestampSegundos Unix da tentativa atual, representados como string.
X-AlthaOne-Signaturesha256= 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.

  1. Capture o corpo bruto antes do parser JSON do framework.
  2. Valide o formato do timestamp e aplique uma tolerância local, por exemplo 300 segundos, mantendo o relógio do servidor sincronizado.
  3. Calcule e compare a assinatura em tempo constante.
  4. Faça o parse JSON e confira se o ID do envelope é igual ao header.
  5. Deduplicate o ID do evento e atualize o pedido na mesma transação local.
  6. 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 erroHTTP usualComo resolver
INVALID_API_KEY401Revisar Bearer, ambiente e revogação. Gere outra credencial pelo painel se a original foi perdida.
PERMISSION_DENIED403Selecionar a permissão necessária em uma credencial adequada.
SELLER_PIX_UNAVAILABLE403Revisar estado ativo e capacidade Pix do cadastro existente.
RESOURCE_NOT_FOUND404Conferir o ID e a credencial do lojista. Recursos de outro lojista também aparecem como inexistentes.
IDEMPOTENCY_CONFLICT409Recuperar o corpo original. Não alterar conteúdo de uma intenção já persistida.
IDEMPOTENCY_KEY_REQUIRED400Enviar chave de 1–128 caracteres no formato permitido; o código também cobre formato inválido do header.
INVALID_DATA400Conferir nomes, tipos, campos extras, centavos inteiros e limites do corpo.
INVALID_PAGINATION400Usar page de 1–10000 e limit de 1–100, ambos inteiros.
INVALID_DATE400Enviar from/to com datas válidas; preferir ISO UTC com Z.
INVALID_STATUS400Usar um estado da modalidade consultada. PAID não é estado de saque.
WEBHOOK_DESTINATION_BLOCKED400 no painelConferir HTTPS, porta, DNS IPv4 público, ausência de credenciais/fragmento e redirecionamento.
RATE_LIMITED429Reduzir concorrência e polling. Aplicar espera progressiva com variação aleatória e respeitar os headers disponíveis.
OPERATION_CONFLICT409Verificar o estado e a configuração da intenção antes de repetir.
PROVIDER_UNAVAILABLE503Preservar a intenção e consultar/repetir com a mesma idempotência; incluir request_id ao suporte.
WALLET_NOT_FOUND404 quando aplicávelRevisar o vínculo da carteira no cadastro existente.
Código dentro do recursoSignificadoAção
PROCESSING_PENDINGProcessamento/condição pendente ou resultado ainda não resolvido.Consultar o mesmo ID; não disparar outra intenção financeira.
INSUFFICIENT_BALANCEDisponí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_UNAVAILABLEDestino ausente, indisponível ou sem condição válida de titularidade.Corrigir o cadastro existente e acompanhar a mesma ordem.
LIMIT_REACHEDLimite 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_CHANGEDA 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_DATAFalha 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_FAILEDCobrança recusada/falha definitiva no provedor.Conferir a operação no painel e tratar o pedido sem considerá-lo pago.
WITHDRAWAL_FAILEDRepasse 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

  1. Separe o HTTP recebido do status financeiro e de error.code.
  2. Confirme se o ID é cobrança ou saída e se pertence à credencial usada.
  3. Compare o valor com o pedido local em centavos.
  4. Consulte o ID sem criar outra intenção.
  5. Verifique se há repasse reservado, destino pendente ou entrega de webhook esgotada.
  6. 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.

  1. Importe o JSON da coleção no Postman.
  2. Confira base_url: https://api.althaone.com.br/api/v1/pix.
  3. Preencha api_key em ambiente privado/local. Não publique a variável com valor real.
  4. Comece por Saldo ou por uma listagem, que não criam movimentação.
  5. Para uma cobrança real autorizada, ajuste charges_idempotency_key e o corpo; depois salve o ID em charge_id.
  6. Para uma saída real autorizada, ajuste withdrawals_idempotency_key e o corpo; salve o ID em withdrawal_id.
  7. 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

  1. Restaure a conexão e confira se o receptor está respondendo 2xx após persistir os eventos.
  2. Consulte as cobranças e saques cujos IDs locais ainda estão pendentes.
  3. Use listagens por janelas de criação para procurar recursos sem vínculo local completo, deduplicando por ID.
  4. Compare referências, valores e moeda antes de atualizar o pedido.
  5. 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

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

POST/charges

Registra 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.

CampoTipoObrigatórioRegra
amount_minorintegerSimMínimo 1. Máximo 2000000000
external_referencestringSimAté 128 caracteres
descriptionstringNãoAté 200 caracteres. Padrão: ""
expires_in_secondsintegerNãoMínimo 60. Máximo 86400. Padrão: 3600
metadataobjectNãoAté 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 retornadoTipoInterpretação
idstringConforme representação do recurso.
external_referencestringConforme representação do recurso.
statusenumValores: PENDING, PAID, EXPIRED, CANCELLED, FAILED
amount_minorintegerMínimo 0
currencyconst BRLConforme representação do recurso.
created_atstring | nullConforme representação do recurso.
updated_atstring | nullConforme representação do recurso.
sourceenumValores: API, AUTOMATIC_PAYOUT
source_charge_idstring | nullConforme representação do recurso.
errorobject | nullConforme representação do recurso.
qr_copy_pastestringConforme representação do recurso.
qr_imagestring | nullData URL PNG pronta para img src.
expires_atstring | nullConforme representação do recurso.
paid_atstring | nullConforme representação do recurso.
automatic_payoutconst trueConforme representação do recurso.
withdrawalobject | nullConforme representação do recurso.
withdrawal.idstringConforme representação do recurso.
withdrawal.statusstringConforme representação do recurso.
commercialobjectConforme representação do recurso.
commercial.gross_minorintegerMínimo 0
commercial.fee_minorinteger | nullMínimo 0
commercial.net_minorinteger | nullMínimo 0
commercial.appliedbooleanConforme representação do recurso.
metadataobjectConforme 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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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

GET/charges

Lista 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âmetroOndeFormato
fromquery{"type":"string"}
toquery{"type":"string"}
statusquery{"type":"string"}
external_referencequery{"type":"string"}
pagequery{"type":"integer","default":1,"minimum":1,"maximum":10000}
limitquery{"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 retornadoTipoInterpretação
itemsarrayConforme representação do recurso.
pageintegerMínimo 0
limitintegerMínimo 0
totalintegerMí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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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

GET/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âmetroOndeFormato
idpath{"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 retornadoTipoInterpretação
idstringConforme representação do recurso.
external_referencestringConforme representação do recurso.
statusenumValores: PENDING, PAID, EXPIRED, CANCELLED, FAILED
amount_minorintegerMínimo 0
currencyconst BRLConforme representação do recurso.
created_atstring | nullConforme representação do recurso.
updated_atstring | nullConforme representação do recurso.
sourceenumValores: API, AUTOMATIC_PAYOUT
source_charge_idstring | nullConforme representação do recurso.
errorobject | nullConforme representação do recurso.
qr_copy_pastestringConforme representação do recurso.
qr_imagestring | nullData URL PNG pronta para img src.
expires_atstring | nullConforme representação do recurso.
paid_atstring | nullConforme representação do recurso.
automatic_payoutconst trueConforme representação do recurso.
withdrawalobject | nullConforme representação do recurso.
withdrawal.idstringConforme representação do recurso.
withdrawal.statusstringConforme representação do recurso.
commercialobjectConforme representação do recurso.
commercial.gross_minorintegerMínimo 0
commercial.fee_minorinteger | nullMínimo 0
commercial.net_minorinteger | nullMínimo 0
commercial.appliedbooleanConforme representação do recurso.
metadataobjectConforme 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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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

POST/withdrawals

O 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.

CampoTipoObrigatórioRegra
amount_minorintegerSimMínimo 1. Máximo 2000000000
external_referencestringSimAté 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 retornadoTipoInterpretação
idstringConforme representação do recurso.
external_referencestringConforme representação do recurso.
statusenumValores: QUEUED, PROCESSING, RECONCILING, COMPLETED, FAILED
amount_minorintegerMínimo 0
currencyconst BRLConforme representação do recurso.
created_atstring | nullConforme representação do recurso.
updated_atstring | nullConforme representação do recurso.
sourceenumValores: API, AUTOMATIC_PAYOUT
source_charge_idstring | nullConforme representação do recurso.
errorobject | nullConforme representação do recurso.
fee_minorintegerMínimo 0
total_debit_minorintegerMínimo 0
destinationobject | nullConforme representação do recurso.
completed_atstring | nullConforme representação do recurso.
end_to_end_idstring | nullNulo 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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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

GET/withdrawals

Inclui 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âmetroOndeFormato
fromquery{"type":"string"}
toquery{"type":"string"}
statusquery{"type":"string"}
external_referencequery{"type":"string"}
pagequery{"type":"integer","default":1,"minimum":1,"maximum":10000}
limitquery{"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 retornadoTipoInterpretação
itemsarrayConforme representação do recurso.
pageintegerMínimo 0
limitintegerMínimo 0
totalintegerMí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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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

GET/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âmetroOndeFormato
idpath{"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 retornadoTipoInterpretação
idstringConforme representação do recurso.
external_referencestringConforme representação do recurso.
statusenumValores: QUEUED, PROCESSING, RECONCILING, COMPLETED, FAILED
amount_minorintegerMínimo 0
currencyconst BRLConforme representação do recurso.
created_atstring | nullConforme representação do recurso.
updated_atstring | nullConforme representação do recurso.
sourceenumValores: API, AUTOMATIC_PAYOUT
source_charge_idstring | nullConforme representação do recurso.
errorobject | nullConforme representação do recurso.
fee_minorintegerMínimo 0
total_debit_minorintegerMínimo 0
destinationobject | nullConforme representação do recurso.
completed_atstring | nullConforme representação do recurso.
end_to_end_idstring | nullNulo 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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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

GET/balance

Consulta 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âmetroOndeFormato

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 retornadoTipoInterpretação
currencyconst BRLConforme representação do recurso.
available_minorintegerMínimo 0
reserved_minorintegerMínimo 0
contractual_reserve_minorintegerMínimo 0
other_pending_minorintegerMínimo 0
updated_atstring | nullConforme 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çãoTratamento
401 / 403Revise a credencial, escopo e habilitação Pix. Não crie outra intenção para contornar o erro.
404Confira o ID, a modalidade e o lojista da credencial.
409Examine o código: conflito de idempotência ou condição da operação não são aprovação.
429Reduza a frequência e use espera progressiva.
503 / timeoutPreserve 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.

Ambientes distintos.

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.

LinguagemRequisitos do exemplo
cURLcURL moderno e shell; chave no ambiente
Node.jsNode.js 20+; fetch nativo
PHPPHP 8.1+ com cURL
PythonPython 3.10+; urllib padrão
GoGo 1.20+; net/http padrão
JavaJava 17+; java.net.http
C#.NET 8+; HttpClient
RubyRuby 3+; net/http padrão

Antes de executar no seu servidor

  1. Selecione o ambiente correto e confira a URL.
  2. Defina ALTHA_API_KEY apenas para LIVE ou ALTHA_DEMO_TOKEN para a simulação.
  3. Em POST, carregue ALTHA_IDEMPOTENCY_KEY do registro já persistido do seu pedido.
  4. Leia HTTP, status e error separadamente.
  5. 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

  1. 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.
  2. Crie uma cobrança; o ID é guardado automaticamente. Repita a mesma solicitação para observar o mesmo ID.
  3. Execute o caso com corpo diferente para conferir o 409.
  4. Simule o pagamento e veja a intenção automática de saída, sem duplicar o saldo.
  5. Marque a saída em conciliação e depois conclua o cenário fictício.
  6. Consulte os eventos assinados e os resultados dos testes.
  7. No Collection Runner, execute a coleção completa na ordem disponibilizada. Ela não chama a API LIVE.

Variáveis e testes

VariávelUso
demo_rootURL fixa do laboratório; não substitua pela API LIVE.
demo_token / demo_webhook_secretCredenciais exclusivamente fictícias capturadas na sessão.
charge_id / withdrawal_idIDs da sessão, capturados nas respostas.
run_idCorrelaciona a execução e mantém chaves estáveis dentro dela.
allow_live_writesA coleção LIVE mantém false até uma decisão financeira explícita de quem opera seu servidor.
api_keySomente 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

  1. O ERP persiste o pedido, o preço e a chave de idempotência.
  2. Seu backend cria a cobrança e devolve os campos públicos ao checkout.
  3. O receptor autenticado grava o evento e marca o pedido pago com validação de valor/moeda.
  4. Uma outbox local libera o produto uma única vez.
  5. 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.