Asaas, com um relógio que você move.

A API v3 do Asaas no localhost, com as regras de negócio capturadas do sandbox real. Avance dias e veja a cobrança acontecer sozinha: a assinatura cobra o cartão salvo no vencimento e retenta quando ele recusa, o boleto vence com multa e juros, o cartão cai em D+32, e cada webhook chega na sua máquina. Depure a máquina de estados de pagamento de ponta a ponta, em vez de chutar.

MOCK ASAAS
reprodução de uma execução
api-sandbox.asaas.com/v3 localhost:45445/v3
  1. 27/10/2026
  2. POST /v3/creditCard/tokenizeCreditCard 200
    MASTERCARD 8829, token 4e0459fd…
  3. PUT /_admin/credit-cards/4e0459fd… 200
    {"approvalsLeft":3}
  4. POST /v3/subscriptions 200
    CREDIT_CARD R$ 129,90 MONTHLY
    primeiro vencimento 05/11
  5. webhook PAYMENT_CREATED 200
  6. 05/11/2026
  7. webhook PAYMENT_CONFIRMED 200
  8. 05/12/2026
  9. webhook PAYMENT_CONFIRMED 200
  10. 05/01/2027
  11. webhook PAYMENT_CONFIRMED 200
  12. 05/02/2027
  13. webhook PAYMENT_CREDIT_CARD_CAPTURE_REFUSED
  14. webhook PAYMENT_CREDIT_CARD_CAPTURE_REFUSED
  15. webhook PAYMENT_CREDIT_CARD_CAPTURE_REFUSED
    cobrança ainda PENDING
  16. 06/02/2027
  17. webhook PAYMENT_CREDIT_CARD_CAPTURE_REFUSED
  18. webhook PAYMENT_CREDIT_CARD_CAPTURE_REFUSED
  19. webhook PAYMENT_CREDIT_CARD_CAPTURE_REFUSED
  20. webhook PAYMENT_OVERDUE 200
  21. 3 pagas, 6 tentativas recusadas, 1 vencida

Uma execução real da imagem: um cartão salvo configurado para aprovar três cobranças paga três faturas no vencimento; a quarta é recusada às 8h, 14h e 20h e mais três vezes no dia seguinte, como o Asaas retenta, e vence. Só o papel é desenhado.

Rode na sua máquina.

Quatro jeitos, do mais rápido ao mais completo. A imagem é pública: nenhum login.

Imagem pronta

O jeito mais rápido: a imagem direto do GHCR.

docker pull ghcr.io/instituto-ama/asaas-mock:latest
docker run -d -p 45445:45445 -e CLOCK_MODE=VIRTUAL_FLOWING \
  ghcr.io/instituto-ama/asaas-mock:latest

Painel e API em localhost:45445, com o relógio virtual ligado. Com o mock do WhatsApp (porta 3010), o mock de SMS (porta 8899) e um Mailpit (porta 8025) no ar, ele acha os três sozinho e cada cobrança já avisa o pagador.

No seu docker-compose

Para subir junto com a sua aplicação, no compose dela. Nada para ligar: o Asaas acha o WhatsApp, o SMS e o Mailpit pelo nome do serviço.

services:
  asaas:
    image: ghcr.io/instituto-ama/asaas-mock:0.11.0
    ports:
      - '45445:45445'
    environment:
      ASAAS_API_KEY: '$$aact_hmlg_local'   # `$$` é um `$` literal no compose
      CLOCK_MODE: VIRTUAL_FLOWING
    depends_on: [whatsapp, mailpit, sms]
    volumes:
      - asaas-data:/data

  whatsapp:
    image: ghcr.io/instituto-ama/whatsapp-mock:0.5.0
    ports:
      - '3010:3010'

  mailpit:
    image: axllent/mailpit:v1.31.4
    ports:
      - '8025:8025'

  sms:
    image: ghcr.io/instituto-ama/sms-mock:0.2.0
    ports:
      - '8899:8899'

volumes:
  asaas-data:

Da sua aplicação: http://asaas:45445/v3. Os avisos ao pagador em localhost:8025 (e-mail), localhost:3010 (WhatsApp) e localhost:8899 (SMS).

Gerar do código

Para rodar a partir do código-fonte, ou depois de alterar o mock.

git clone https://github.com/Instituto-AMA/ama4devs.git
cd ama4devs
docker build -t asaas-mock mocks/asaas
docker run -d -p 45445:45445 -e CLOCK_MODE=VIRTUAL_FLOWING asaas-mock

Painel e API em localhost:45445; o WhatsApp, o SMS e o Mailpit, se estiverem no ar, ele acha sozinho

Subir tudo junto

Junto com o mock do WhatsApp, o mock de SMS e o Mailpit, já conectados: cada cobrança avisa o pagador por e-mail, por WhatsApp e por SMS.

git clone https://github.com/Instituto-AMA/ama4devs.git
cd ama4devs
make setup

Asaas em localhost:45445, WhatsApp em localhost:3010, SMS em localhost:8899, e-mails em localhost:8025

O relógio é o diferencial.

No sandbox real o tempo é real: um pagamento no cartão cai em 32 dias e um boleto vencido leva um dia para aparecer. Aqui cada dia simulado roda os jobs que o Asaas roda, em ordem, e você vê seu sistema reagir a meses de cobrança em segundos.

Cada dia, um tick

  • Assinaturas criam as próximas cobranças, 40 dias antes.
  • O cartão salvo é cobrado no vencimento às 8h, 14h e 20h, e retentado no dia seguinte.
  • Cobranças em aberto viram OVERDUE; a multa uma vez, os juros por dia.
  • Boleto cai em D+1 útil, cartão em D+32, débito em D+3.
  • Webhooks disparam e retentam no cronograma do próprio Asaas.
  • Lembretes e avisos ao pagador saem por e-mail, WhatsApp e SMS.
  • Chaves de API com data de expiração expiram.
curl -X POST localhost:45445/_admin/clock/advance \
  -H 'content-type: application/json' -d '{"days": 32}'

Três modos

  • REAL: a hora da máquina, como o sandbox; o relógio não deixa pular.
  • VIRTUAL_FLOWING: o tempo corre, e você pula para a frente quando quiser. O padrão do compose do repositório.
  • VIRTUAL_FROZEN: o relógio só anda quando o teste manda. Feito para CI: a mesma execução dá o mesmo resultado.
O relógio virtual no painel do simulador: a hora e o modo, e os botões para avançar um, sete ou trinta e dois dias, rodar um tick ou voltar ao presente.

Casos de uso.

Cenários que o seu código de pagamento precisa aguentar, cada um a poucas chamadas de distância. Eles partem do container do quick start: chave $aact_hmlg_local, relógio em VIRTUAL_FLOWING.

Um cartão salvo que paga três meses e falha no quarto

Prova a máquina de estados da sua assinatura de ponta a ponta, em vez de no chute: faturas pagas pelo cartão salvo no vencimento, e depois uma renovação recusada. O Asaas tenta o cartão às 8h, 14h e 20h no vencimento e mais três vezes no dia seguinte; cada recusa é um PAYMENT_CREDIT_CARD_CAPTURE_REFUSED com a cobrança ainda PENDING, depois PAYMENT_OVERDUE e nenhuma tentativa a mais. O seu app avisa o cliente na primeira recusa, mantém o acesso durante as retentativas e só suspende quando a cobrança vence?

# um cartão salvo que aprova três cobranças e depois recusa
curl -X PUT localhost:45445/_admin/credit-cards/<creditCardToken> \
  -H 'content-type: application/json' -d '{"approvalsLeft":3}'
curl localhost:45445/v3/subscriptions -H 'access_token: $aact_hmlg_local' \
  -H 'content-type: application/json' -d '{"customer":"cus_…","billingType":"CREDIT_CARD",
  "value":129.9,"nextDueDate":"2026-11-05","cycle":"MONTHLY","creditCardToken":"<creditCardToken>",
  "creditCardHolderInfo":{…},"remoteIp":"127.0.0.1"}'
curl -X POST localhost:45445/_admin/clock/advance -d '{"days":102}' -H 'content-type: application/json'
# de 27/10 a 06/02, o dia seguinte ao quarto vencimento:
# → 3 × PAYMENT_CONFIRMED, depois 6 × PAYMENT_CREDIT_CARD_CAPTURE_REFUSED e PAYMENT_OVERDUE

Troque o cartão com PUT /v3/subscriptions/{id}/creditCard durante as retentativas e a próxima tentativa captura com o novo. O cronograma é configuração: CARD_CAPTURE_SCHEDULE ou a página de cartões de teste do painel.

Uma cobrança no cartão que cai 32 dias depois

Prova que o seu fluxo de pedido distingue PAYMENT_CONFIRMED (o cliente pagou) de PAYMENT_RECEIVED (o dinheiro é seu), e que a tela de saldo espera o segundo.

curl localhost:45445/v3/payments -H 'access_token: $aact_hmlg_local' \
  -H 'content-type: application/json' -d '{"customer":"cus_…","billingType":"CREDIT_CARD",
  "value":350,"dueDate":"2026-10-27","creditCardToken":"…","remoteIp":"127.0.0.1"}'
# → CONFIRMED, netValue 339.04, estimatedCreditDate 2026-11-30
curl -X POST localhost:45445/_admin/clock/advance -d '{"days":34}' -H 'content-type: application/json'
# → webhook PAYMENT_RECEIVED; saldo 339.04

Um boleto pago com atraso, com multa e juros

Prova que você cobra o que o Asaas cobra: a multa de 2% uma vez, no primeiro dia de atraso, e os juros de 1% ao mês pro rata sobre o mês comercial de 30 dias. R$ 120,00 pagos seis dias depois viram R$ 122,64.

curl localhost:45445/v3/payments -H 'access_token: $aact_hmlg_local' \
  -H 'content-type: application/json' -d '{"customer":"cus_…","billingType":"BOLETO",
  "value":120,"dueDate":"2026-12-02","fine":{"value":2},"interest":{"value":1}}'
curl -X POST localhost:45445/_admin/clock/advance -d '{"days":6}' -H 'content-type: application/json'
# → webhook PAYMENT_OVERDUE em 03/12
curl -X POST localhost:45445/v3/sandbox/payment/pay_…/confirm -H 'access_token: $aact_hmlg_local'
# → CONFIRMED, value 122.64; um dia depois, PAYMENT_RECEIVED

O seu webhook responde 204

Prova que o seu endpoint responde o que o Asaas aceita. Só HTTP 200 conta: 201 e 204 são falhas, a regra real. Uma entrega com falha é retentada 15 vezes na tabela do Asaas (na hora, +30 s, +1 min, +3 min 30 s, +5 min, +15 min, +25 min, +1 h cinco vezes, +2 h duas vezes, +3 h). Com SEQUENTIALLY, o evento de trás espera; depois da 15ª o webhook fica interrupted e a fila inteira para.

curl localhost:45445/_admin/webhooks/deliveries   # cada tentativa, com o status e o corpo que o seu endpoint devolveu

Uma assinatura, um ano à frente

Prova a sua lógica de recorrência em doze ciclos, em segundos. A primeira cobrança nasce com a assinatura; as seguintes nascem 40 dias antes do vencimento, como no sandbox real.

curl localhost:45445/v3/subscriptions -H 'access_token: $aact_hmlg_local' \
  -H 'content-type: application/json' -d '{"customer":"cus_…","billingType":"BOLETO",
  "value":99.9,"nextDueDate":"2026-11-10","cycle":"MONTHLY"}'
curl -X POST localhost:45445/_admin/clock/advance -d '{"days":365}' -H 'content-type: application/json'
curl localhost:45445/v3/subscriptions/sub_…/payments -H 'access_token: $aact_hmlg_local'

Um cartão salvo que para de funcionar

No sandbox real, um cartão que recusa nunca vira token, então um cartão salvo sempre aprova. Aqui o cartão de teste 4000000000000341 tokeniza e depois recusa toda cobrança feita com o token, com o erro real (invalid_action, mesma frase): a renovação que falha meses depois, antes de acontecer em produção.

curl localhost:45445/v3/payments -H 'access_token: $aact_hmlg_local' \
  -H 'content-type: application/json' -d '{"customer":"cus_…","billingType":"CREDIT_CARD",
  "value":99.9,"dueDate":"2026-11-10","creditCardToken":"<token do 4000000000000341>"}'
# → 400 {"errors":[{"code":"invalid_action","description":"Transação não autorizada. …"}]}

Parcelas que fecham a conta

Prova que a sua divisão de um total bate com a do Asaas: a sobra do arredondamento vai para a última parcela, a taxa do cartão trunca (R$ 350 em 12x dá R$ 1,20 de taxa por parcela, não R$ 1,21), e no cartão a parcela n cai em D+32×n.

curl localhost:45445/v3/payments -H 'access_token: $aact_hmlg_local' \
  -H 'content-type: application/json' -d '{"customer":"cus_…","billingType":"BOLETO",
  "totalValue":350,"installmentCount":12,"dueDate":"2026-11-10"}'

O que o pagador recebe

Todo evento de cobrança avisa o pagador por e-mail (no Mailpit), por um template de WhatsApp (no mock do WhatsApp) e por SMS (no mock de SMS, com o remetente ASAAS). Um parcelamento é avisado uma vez só. Suba o compose do repositório e abra as telas lado a lado.

Uma cobrança com o link da fatura, os botões de pagamento e as notificações ao pagador: o e-mail enviado ao Mailpit e o template de WhatsApp enviado ao mock do WhatsApp.

O painel: veja o que a API real esconde.

Abra http://localhost:45445. Gerencie clientes, cobranças, pagamentos com cartão, webhooks, notificações e simule a passagem do tempo com o relógio virtual.

Provado contra o sandbox real.

As regras foram capturadas do sandbox do Asaas, e uma suíte de paridade as reproduz campo a campo, até o conjunto exato de chaves de cada objeto. A captura derrubou 20 regras que a documentação sugere. Toda operação da especificação oficial responde: 135 de 213 com motor de negócio, o resto com 501 nomeando a operação, nunca dado falso.