Local mock servers of two third-party APIs, for development, automated tests and CI, as Docker images on GHCR:
| Mock | What it simulates | Image |
|---|---|---|
| WhatsApp mock | WhatsApp Cloud API (Meta Graph API v23.0, WhatsApp Business Platform): messages, message templates, media, voice calls (Calling API with WebRTC), phone numbers and WABAs, webhooks, and a WhatsApp Web like panel | docker pull ghcr.io/instituto-ama/whatsapp-mock:latest |
| Asaas mock | Asaas API v3 (sandbox simulator of the Brazilian payment gateway): customers, charges with Pix, boleto and credit card, installments, subscriptions, webhooks delivered to localhost, payer e-mail, WhatsApp and SMS notifications |
docker pull ghcr.io/instituto-ama/asaas-mock:latest |
| Generator | Fake Brazilian data for tests: people, companies, CPF, CNPJ, CNH, PIS, RG, addresses with real CEP ranges, credit cards, bank accounts, Pix keys and charges, the same for the same seed; populates the Asaas mock with customers and charges in one call. Its UI also runs in the browser at instituto-ama.github.io/ama4devs/generator/app | docker pull ghcr.io/instituto-ama/generator-mock:latest |
| SMS mock | SMS gateway (native API, Twilio-compatible, simple GET/POST): messages, status and inbound webhooks, deterministic magic numbers, segment counting, OTP extraction, and a phone inbox panel | docker pull ghcr.io/instituto-ama/sms-mock:latest |
Documentation site: instituto-ama.github.io/ama4devs (in Portuguese).
This repository provides reproducible local mock containers with high behavioral fidelity. Developers and CI pipelines can run end-to-end payment flows and messaging integrations without creating vendor accounts, without managing external credentials, and with full support for delivering webhooks directly to localhost.
Start all mocks and create your local .env:
make setupTo configure another application to point to the running mocks, pass TARGET_ENV:
make setup TARGET_ENV=../my-app/.envYou can optionally specify a profile (default is generic):
make setup PROFILE=generic TARGET_ENV=../my-app/.envOnce running, access the web inspection panels:
- Asaas Simulator Panel: http://localhost:45445
- WhatsApp Web Panel: http://localhost:3010
- Generator: http://localhost:45450 (starts with the Asaas mock;
GENERATOR_ENABLED=0in.envkeeps it down). The Asaas mock finds it by itself and shows Integrations › Generator, which fills the selected account with customers and charges - SMS Panel: http://localhost:8899 (live phone inbox to inspect incoming messages, extract OTP codes, reply as the handset, and simulate delivery outcomes; starts with the Asaas mock, which sends the payer SMS to it —
SMS_ENABLED=0in.envkeeps it down)
The repository supports two development workflows:
- 100% local and mocked: No external accounts, internet access, or credentials needed.
- Builds and starts the containerized mocks with Docker Compose.
- Generates
.envfrom.env.exampleif not present. - Writes mock endpoints (e.g.
http://localhost:45445/v3andhttp://localhost:3010) and fake master keys ($aact_hmlg_local_master) toTARGET_ENV. - Webhook registration: The app registers its webhook in the mock exactly as it does against the real Asaas (
POST /v3/webhooks); the mock delivers each event right after the change that caused it, andlocalhosttargets are rewritten to the host.
- Points
TARGET_ENVat the official Asaas sandbox (https://api-sandbox.asaas.com/v3) and Meta Graph API (https://graph.facebook.com). - Prompts for your Asaas sandbox API key (hidden input; must start with
$aact_hmlg_). - Verifies the key with
GET /myAccount/statusagainst the official Asaas sandbox API. - Prompts for your application's public HTTPS base URL (e.g. from
cloudflared,ngrok, orzrok) and an alert email. - Automatically registers this developer's webhook in the Asaas sandbox and cleans up older webhooks previously registered by the same developer.
- Generates a random webhook authorization token and registers the webhook in the sandbox.
- Credential security: All sandbox credentials are saved exclusively to
.env.sandbox(permission0600, gitignored, and never committed to the repository). Secrets are masked in console output and never passed via command-line arguments. - WhatsApp is not configured by this flow:
setup-sandboxonly switchesWHATSAPP_GRAPH_URLto Meta's Graph API; the Meta access token and the WhatsApp webhook belong to the app.
| Target | Description |
|---|---|
help |
List the targets |
env |
Create .env from .env.example (never overwrites) |
setup |
Start the mocks and point TARGET_ENV at them (no credential needed) |
setup-sandbox |
Point TARGET_ENV at the official Asaas sandbox and Meta Graph API |
up |
Start the mocks |
down |
Stop the mocks |
restart |
Restart the mocks |
logs |
Follow logs: make logs s=<service> |
ps |
List the mock containers |
status |
Health of each mock, one line each |
build |
Build the mock images |
publish |
Build linux/amd64 + linux/arm64 and push to GHCR: make publish MOCKS=asaas (needs docker login ghcr.io) |
env-mock |
Write the mock env keys into TARGET_ENV (or print them) |
env-sandbox |
Write the sandbox env keys into TARGET_ENV (or print them) |
test |
Run the tests of both mocks, one after the other |
clean |
Remove containers and volumes (FORCE=1 skips the prompt) |
Each mock is published to GHCR for linux/amd64 and linux/arm64, so another project runs it
without cloning this repository:
| Mock | Image |
|---|---|
| Asaas | ghcr.io/instituto-ama/asaas-mock |
ghcr.io/instituto-ama/whatsapp-mock |
|
| Generator | ghcr.io/instituto-ama/generator-mock |
| SMS | ghcr.io/instituto-ama/sms-mock |
One image per version, with two tags:
<version>: theversionof the mock'spackage.json. It is pushed once and never moves; pin it.latest: the newest version, the same image.
The registry keeps nothing else: no image without a version.
How it is published:
- The
publish-mocksworkflow (.github/workflows/publish-mocks.yml) runs on every push tomasterthat changesmocks/**.scripts/changed-mocks.shpicks the mocks whose directory changed (a change to the workflow or toscripts/publish.shpicks all of them), the mock's tests run, thenscripts/publish.shbuilds and pushes. A mock that did not change is not rebuilt. It can also be started by hand (Actions → publish-mocks → Run workflow) with the list of mocks. - By hand:
docker login ghcr.iowith a token that haswrite:packages, thenmake publish MOCKS=asaas(orMOCKS="asaas whatsapp"). It refuses a mock directory with uncommitted changes, so an image always matches a commit. It builds with its own buildx builder (ama4devs); the foreign platform runs under emulation and is slow. - Releasing a version: bump
versionin the mock'spackage.jsonin the change that alters it. A push without a bump publishes nothing for that mock, and the workflow warns.
The Asaas mock provides an in-depth simulation of the Asaas payment platform.
- API Base Path:
http://localhost:45445/v3 - Dashboard Panel:
http://localhost:45445(accounts, balances, API keys, customers, charges, subscriptions, installment plans and ledger entries; create customers and charges of the three kinds, receive in cash). - Payer Notifications: every charge event e-mails the payer (to Mailpit, UI on
http://localhost:8025, started with theasaasprofile), sends a WhatsApp template (to the WhatsApp mock, whosetemplates/asaas_*.jsonhold them) and sends an SMS (to the SMS mock, UI onhttp://localhost:8899, also started with theasaasprofile; the Asaas mock finds it by itself and sends with the senderASAAS). The SMS follows the customer'ssmsEnabledForCustomerflag and needs a mobile phone. The charge dialog of the panel lists the e-mail and the WhatsApp template and forces one on demand. - Invoice Page: every charge's
invoiceUrl(http://localhost:45445/i/<id>) opens the payer's page, where the charge is paid by Pix, bank slip or a test card through the same path as a real payment.INVOICE_BASE_URLsets its address when the host port differs. - Virtual Clock: Controlled by
ASAAS_CLOCK_MODE(REAL,VIRTUAL_FLOWING, orVIRTUAL_FROZEN).- Advance time:
POST /_admin/clock/advancewith{"days": 32}to trigger D+32 credit card settlements or overdue calculations in milliseconds. - Set specific date:
POST /_admin/clock/setwith{"date": "2026-08-01"}. - Run tick:
POST /_admin/tick.
- Advance time:
- Seeded Accounts:
- The default root account is created with master key
${ASAAS_API_KEY}(default:$aact_hmlg_local_master). - Additional root accounts can be seeded on boot using
ASAAS_SEED_EXTRA_ACCOUNTS(format:apiKey|name|cpfCnpjseparated by;), allowing multiple tenants to run against the same mock instance with stable keys.
- The default root account is created with master key
- Local Webhook Delivery:
- Inside Docker containers,
localhostrefers to the container itself. The mock usesWEBHOOK_LOCALHOST_REWRITE=host.docker.internalandextra_hoststo rewrite loopback webhook targets to reach the host machine. - Webhooks include the
asaas-access-tokenheader. - Webhook
authTokenvalues must be between 32 and 255 characters, matching the official Asaas specification. - Immediate delivery: mutating operations (
POST,PUT,PATCH,DELETE) under/v3,/_mock, and/_admintrigger immediate dispatch viaScheduler.dispatchNow()inmocks/asaas/src/scheduler/scheduler.ts(called fromonAfterResponseinmocks/asaas/src/http/app.ts), delivering webhooks right after successful commits without waiting for the next timer tick. Retries run on every tick with backoff up to 15 attempts.
- Inside Docker containers,
Key-free helper routes mounted under /_mock allow deterministic control over payment states in automated tests and local verification:
- List payments:
curl http://localhost:45445/_mock/payments?limit=10 - Confirm a payment:
curl -X POST http://localhost:45445/_mock/payments/pay_123456789012/confirm
- Receive / settle a payment immediately:
curl -X POST http://localhost:45445/_mock/payments/pay_123456789012/receive
- Mark payment as overdue:
curl -X POST http://localhost:45445/_mock/payments/pay_123456789012/overdue
- Fail payment (alias for overdue):
curl -X POST http://localhost:45445/_mock/payments/pay_123456789012/fail
- Refund payment (full or partial):
curl -X POST http://localhost:45445/_mock/payments/pay_123456789012/refund \ -H 'Content-Type: application/json' \ -d '{"value": 50.00}'
A tiny service with no dependency (Bun, port 45450) that generates valid fake Brazilian data and fills the Asaas mock. See mocks/generator/README.md.
- API:
GET /v1/<kind>?count=10&seed=demo&formatted=trueforperson,company,address,cpf,cnpj,cnh,pis,rg,credit-card,bank-account,pix-key,charge;GET /v1/validate/cpf/11144477735. The same seed (andnow) returns the same data. - Asaas population:
POST /_mock/asaas/populatewith{"people": 5, "companies": 2, "chargesPerCustomer": 2, "confirm": 0.5}creates customers and charges through the Asaas API and confirms part of them;GENERATOR_ASAAS_SEED_ON_BOOT=people=10,companies=3,charges=2,confirm=0.5in.envdoes it once on boot, while the Asaas mock has no customer. - Joins the Asaas mock by itself: each finds the other on boot and every 10 s (compose service name, this
machine, the Docker host), like Mailpit and the WhatsApp mock. The Asaas panel then has Integrations › Generator
to populate the selected account.
GENERATOR_URL=(empty) on the Asaas mock turns it off and hides the tab. - UI:
http://localhost:45450, and the same page on GitHub Pages, generating in the browser.
A lightweight SMS gateway simulator with no dependencies (Bun, port 8899) for local development, automated tests and CI. See mocks/sms/README.md.
- APIs: Native
POST /v1/messages(to,body,from,callbackUrl) andGET /v1/messages(withwaitlong-polling for tests); Twilio-compatiblePOST /2010-04-01/Accounts/{sid}/Messages.json; and simpleGET|POST /sms/send. - Message Lifecycle & Magic Numbers: Follows
queued→sent→delivered(afterSMS_DELIVERY_DELAY_MS, default 1500 ms). Magic numbers by last four digits simulate deterministic outcomes:0001fails (30003),0002is undelivered (30005),0003rejected at API call (HTTP 400),0004remains stuck insent, and0005filtered (failed, 30007). - Segments & OTP: Counts GSM-7 (160/153) and UCS-2 (70/67) segments, and automatically extracts OTP verification codes.
- Webhooks: Dispatches status and inbound webhooks (JSON or Twilio-style form encoding) signed with
x-sms-signature: sha256=<hmac>whenSMS_WEBHOOK_SECRETis set, with loopback rewriting support. - Control Routes: Mounted under
/_mock/sms/(settings,inbound,messages/:id/status,reset,webhooks/deliveries,demo). - UI: http://localhost:8899 — a real-time phone inbox panel over WebSocket (
/ws) allowing you to inspect messages, view OTP chips, reply as the handset, and manually change message statuses.
The WhatsApp mock simulates the Meta WhatsApp Business Cloud API.
- Graph API:
/v23.0/..., every operation of Meta's own OpenAPI specification (mocks/whatsapp/spec/v23.0), validated and answered like Meta, with an access token (Authorization: Bearer …, any value). What only a test needs travels in optionalX-Mock-*headers; what Meta has no call for is under/settings. - Web Panel: http://localhost:3010 (conversations with every message type, files, the template builder, the API reference).
- State: conversations, webhook calls, settings, webhooks, templates and media survive a restart (SQLite and files in the
whatsapp-datavolume,/app/data). - The user writes (the same send call, from the user's phone):
curl -X POST http://localhost:3010/v23.0/123456789/messages \ -H 'Authorization: Bearer test' \ -H 'Content-Type: application/json' \ -H 'X-Mock-From: 5511999999999' \ -H 'X-Mock-Contact-Name: Maria Souza' \ -d '{"messaging_product": "whatsapp", "type": "text", "text": {"body": "Hello, I need information"}}'
- Webhook Configuration:
- Set via environment variable
WHATSAPP_WEBHOOK_URLin.env. - Per number, Meta's call:
POST /v23.0/{phone-number-id}with{"webhook_configuration": {"override_callback_uri": "http://localhost:8080/webhooks/whatsapp", "verify_token": "…"}}; the mock checks the address withhub.challengefirst (X-Mock-Skip-Callback-Verification: trueskips it). - Loopback targets are rewritten to
host.docker.internalusingWEBHOOK_LOCALHOST_REWRITE.
- Set via environment variable
- HMAC Signatures:
- When
WHATSAPP_WEBHOOK_SECRETis set, outgoing webhook deliveries include thex-hub-signature-256header calculated assha256=HMAC_SHA256(body, secret).
- When
Ama-Hub integrates these mocks directly into its development lifecycle:
- In Ama-Hub,
make devstarts these mocks from the published images, at the versions pinned in itsdeploy/compose/docker-compose.dev.yml. WithMOCKS_BUILD=1it builds them from the sibling checkout instead, to try a change before it is published. - Running
make sandboxin Ama-Hub configures it to communicate with the official provider sandboxes. - See Ama-Hub's local environment documentation at
docs/runbooks/local-environment.mdfor details on how Ama-Hub orchestrates development services.
- Bound to Loopback: All host ports are bound exclusively to
127.0.0.1(127.0.0.1:45445,127.0.0.1:3010and127.0.0.1:8899). They must never be exposed on0.0.0.0or public networks. - Local Route Guard: Every mock implements a strict local guard. The Asaas mock guards
/,/_admin/*and/_mock/*; the generator guards/and/_mock/*; the WhatsApp mock guards every route it serves, including the/wsupgrade. Each rejects requests with untrustedHostheaders (DNS rebinding) and, on writes, untrustedOriginheaders (cross-site request attacks); the opaquenullorigin is refused as well. Besides the loopback names (localhost,127.0.0.1,::1) andhost.docker.internal, the guard admits every hostname inMOCK_ALLOWED_HOSTS— the Compose service names (asaas,whatsapp) are the ones to use, so a mock reached by service name is not rejected. - Fake Keys: All default API keys in
.env.exampleand tests are non-functional mock placeholders (e.g.$aact_hmlg_local_master). Never commit real credentials to this repository.
mocks/asaas: MIT License (mocks/asaas/LICENSE).mocks/whatsapp: Apache License 2.0 (mocks/whatsapp/LICENSE).mocks/generator: MIT License (mocks/generator/LICENSE).