Local testing
Receive Agoo webhooks on your own machine while you build, with a tunnel, test events, the planned Agoo CLI or requests you sign yourself.
This is a published preview. Names and fields may still change before general availability; changes will be listed in the changelog.
Agoo delivers webhooks only to public HTTPS URLs, so it can't reach http://localhost:3000 directly. While you build, use one of these approaches:
| Approach | Good for | Available |
|---|---|---|
| A tunnel to your machine | Real deliveries, retries and test events end to end | With the API (P9) |
| Test events | Checking signature handling on demand | With the API (P9) |
| The Agoo CLI | Forwarding test-mode events without a public URL | Planned |
| Requests you sign yourself | Unit tests and offline work, with no Agoo account | Any time |
Do all of this in test mode, with a test key and a test endpoint. Test events never touch real visitors.
Use a tunnel
A tunnel gives your local server a temporary public HTTPS address. Any tunnelling tool works; two common ones:
cloudflared tunnel --url http://localhost:3000It prints an address such as https://<random-words>.trycloudflare.com.
Then register that address as a test-mode endpoint, using a test key:
curl https://api.agoo.ardent.africa/v1/webhook-endpoints \
-H "Authorization: Bearer $AGOO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://YOUR-TUNNEL-ADDRESS/webhooks/agoo",
"description": "Local development",
"enabled_events": ["*"]
}'Save the secret from the response as AGOO_WEBHOOK_SECRET for your local server. Free tunnel addresses usually change each time you start the tunnel; update the endpoint with PATCH/webhook-endpoints/{whep_id} and { "url": "…" } rather than creating a new one, so the secret stays the same.
To make events happen, drive test visits through their life with the API (see environments), or send a test event.
Send a test event
POST/webhook-endpoints/{whep_id}/test sends one signed event of the type you choose straight away, with a sample object, and returns your endpoint's response:
curl -X POST https://api.agoo.ardent.africa/v1/webhook-endpoints/whep_01m2jf0cg0ffyrmjbzsk9t0gtc/test \
-H "Authorization: Bearer $AGOO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "type": "booking.created" }'{
"endpoint_id": "whep_01m2jf0cg0ffyrmjbzsk9t0gtc",
"event_id": "evt_01m4wwgwc0f8y8gskxtb18r86q",
"type": "booking.created",
"attempted_at": "2026-10-14T09:40:00Z",
"outcome": "succeeded",
"status_code": 200,
"duration_ms": 182,
"error": null
}Test events are sent once, aren't retried and don't appear in GET /events. They work on disabled endpoints too, so you can check a fix before you re-enable one.
Forward events with the Agoo CLI
Planned
The Agoo CLI (@ardent-africa/agoo-cli) is planned to ship with or after the API in phase P9. The commands below show how it is designed to work.
agoo listen will receive your organisation's test-mode events and forward them to a URL on your machine, signed like real deliveries, with no tunnel or public URL:
agoo login
agoo listen --forward-to http://localhost:3000/webhooks/agooIt prints a signing secret for the session; use it as AGOO_WEBHOOK_SECRET while agoo listen runs. In another terminal, agoo trigger makes an event happen in test mode:
agoo trigger visit.checked_inSee the CLI reference for every command and flag.
Sign requests yourself
For unit tests, or to work without an Agoo account or network, sign a saved payload with any whsec_ secret your local server is configured with, and post it. Copy a payload from the event catalogue or an event's page in the API reference into event.json.
// Usage: node sign-and-send.mts event.json [url]
import { createHmac } from "node:crypto"
import { readFileSync } from "node:fs"
const secret = process.env.AGOO_WEBHOOK_SECRET! // the same secret your local server verifies with
const body = readFileSync(process.argv[2] ?? "event.json") // the exact bytes to send
const url = process.argv[3] ?? "http://localhost:3000/webhooks/agoo"
const id: string = JSON.parse(body.toString("utf8")).id
const timestamp = Math.floor(Date.now() / 1000).toString()
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64")
const signature = createHmac("sha256", key).update(`${id}.${timestamp}.`).update(body).digest("base64")
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"webhook-id": id,
"webhook-timestamp": timestamp,
"webhook-signature": `v1,${signature}`,
},
body,
})
console.log(res.status, await res.text())node sign-and-send.mts event.jsonWhat to test
Before you go live, check that your endpoint:
- accepts a correctly signed request and returns
2xxwithin 15 seconds; - rejects a request whose body was changed by one character, with
401; - rejects a request whose
webhook-timestampis more than 5 minutes old; - processes the same
webhook-idonly once when it arrives twice; - copes with events out of order, such as
visit.checked_outbeforevisit.checked_in; - returns
2xxfor an event type it doesn't handle; - still verifies during a secret rotation, when
webhook-signatureholds two signatures.
Related
Verify signatures
Check that every webhook really came from Agoo, with complete verification code for TypeScript, Python, PHP and Go, and rotate secrets without downtime.
MCP server
Let AI assistants such as Claude, ChatGPT, Cursor and VS Code read and act on your Agoo data, within each person's permissions.