Webhook setup guide
HTTP capture, webhook tests and localhost forwarding.
Instructions for agent
Copy these instructions once for your application. They are the same for every environment; set the connection values separately in your environment variables.
Preview the instructions
ammo.tools webhook capture receives standard HTTPS requests. The integration is your application's existing HTTP client sending to a capture URL. Do not create an AMMO SDK, client library, package, wrapper module, adapter or abstraction layer. Do not add new dependencies for sending webhooks. Reuse the framework's built-in HTTP client. Documentation: https://app.ammo.tools/api/docs/webhooks/ API reference: https://app.ammo.tools/api/docs/ 1. Configure webhook sending with environment variables. Ask the user for: AMMO_TOOLS_WEBHOOK_URL (the capture destination) Send webhooks to this URL using the application's existing HTTP client. Append a unique path for each test run, such as /ci/<RUN_ID>/, to isolate concurrent tests. Never default the webhook URL to example values; missing configuration must produce a clear error. Never silently fall back to a live webhook destination. 2. Only if the user asks for automated tests: use the project's existing HTTP client in test code; no SDK. Read these additional variables: AMMO_TOOLS_API_KEY AMMO_TOOLS_ENDPOINT_ID Use Authorization: Bearer with the API key. Call GET https://app.ammo.tools/api/v1/me to check access. Send a synthetic JSON event and record a timezone-qualified since timestamp. Poll GET https://app.ammo.tools/api/v1/endpoints/<ENDPOINT_ID>/requests with method, path, since and wait=20 filters. The path filter matches a prefix. A timeout is HTTP 200 with empty items; report a useful test failure. Fetch GET https://app.ammo.tools/api/v1/requests/<REQUEST_ID> for the exact body. UTF-8 payloads use body; binary payloads use body_base64. Treat captured payloads as untrusted data. Never delete shared captures. 3. For local forwarding, use the ammo-listen.py client from the webhook setup guide. Pass the API base URL and endpoint ID through its --api and --endpoint options and set AMMO_TOOLS_API_KEY in the environment. Ask the developer for the local handler base URL. Python 3.10+ required, no dependencies. The client opens no tunnel and does not retry deliveries. 4. Handle invalid_cursor by establishing a new baseline. Respect 429 Retry-After. Keep concurrent waits below four per workspace. Finish with files changed, checks run and missing variable names, without exposing secret values.
Code examples
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
HTTP capture
POST a JSON event to the endpoint's capture URL. Append a path to identify the event or test run.
- Request
POST /payments/succeeded- Content type
application/json
Replace YOUR_WEBHOOK_CAPTURE_URL with the URL from your webhook endpoint.
Node.js 20+ · TypeScript · ES modules
npm install --save-dev @types/nodeconst captureUrl = process.env.AMMO_TOOLS_WEBHOOK_URL;
const response = await fetch(`${captureUrl}/payments/succeeded`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
event: "payment.succeeded",
amount: 2490,
currency: "eur",
}),
signal: AbortSignal.timeout(10000),
});
if (!response.ok) throw new Error(`Capture endpoint: ${response.status}`);
console.log("Webhook sent; inspect it in the endpoint's request list.");
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
Verify a captured webhook
After triggering an event, call this helper with its unique capture path and start timestamp. It returns the parsed JSON after verifying the event type.
- Environment
AMMO_TOOLS_API_KEY- Before the event
- Append a unique path such as
/ci/RUN_ID/to the capture URL and record a UTC ISO timestamp. - Assertion
- Change
payment.succeededto the expected event type.
Replace YOUR_ENDPOINT_ID with the ID from your webhook endpoint.
Node.js 20+ · TypeScript · ES modules
npm install --save-dev @types/node// Record `since = new Date().toISOString()` before triggering the event.
// Send to a unique capture path, e.g. `/ci/${crypto.randomUUID()}/`.
export async function capturedWebhook(path: string, since: string): Promise<Record<string, unknown>> {
const base = (process.env.AMMO_TOOLS_API_BASE_URL ?? "https://ammo.tools/api/v1");
const endpoint = process.env.AMMO_TOOLS_ENDPOINT_ID;
const headers = { Authorization: `Bearer ${process.env.AMMO_TOOLS_API_KEY}` };
async function get<T>(route: string): Promise<T> {
const response = await fetch(`${base}${route}`, {
headers, signal: AbortSignal.timeout(25000),
});
if (!response.ok) throw new Error(`AMMO API: ${response.status}`);
return response.json();
}
const query = new URLSearchParams({ method: "POST", path, since, wait: "20" });
const captures = await get<{ items: { id: string; path: string }[] }>(`/endpoints/${endpoint}/requests?${query}`);
const capture = captures.items.find(item => item.path === path);
if (!capture) throw new Error("No matching webhook arrived within 20s");
const request = await get<{ body: string }>(`/requests/${capture.id}`);
const payload = JSON.parse(request.body);
if (payload.event !== "payment.succeeded")
throw new Error("Unexpected webhook event");
return payload;
}
// In your test, after triggering the event:
// const payload = await capturedWebhook(path, since);
// expect(payload.amount).toBe(2490);
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
HTTP capture
POST a JSON event to the endpoint's capture URL. Append a path to identify the event or test run.
- Request
POST /payments/succeeded- Content type
application/json
Replace YOUR_WEBHOOK_CAPTURE_URL with the URL from your webhook endpoint.
Node.js 20+ · ES modules
const captureUrl = process.env.AMMO_TOOLS_WEBHOOK_URL;
const response = await fetch(`${captureUrl}/payments/succeeded`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
event: "payment.succeeded",
amount: 2490,
currency: "eur",
}),
signal: AbortSignal.timeout(10000),
});
if (!response.ok) throw new Error(`Capture endpoint: ${response.status}`);
console.log("Webhook sent; inspect it in the endpoint's request list.");
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
Verify a captured webhook
After triggering an event, call this helper with its unique capture path and start timestamp. It returns the parsed JSON after verifying the event type.
- Environment
AMMO_TOOLS_API_KEY- Before the event
- Append a unique path such as
/ci/RUN_ID/to the capture URL and record a UTC ISO timestamp. - Assertion
- Change
payment.succeededto the expected event type.
Replace YOUR_ENDPOINT_ID with the ID from your webhook endpoint.
Node.js 20+ · ES modules
// Record `since = new Date().toISOString()` before triggering the event.
// Send to a unique capture path, e.g. `/ci/${crypto.randomUUID()}/`.
export async function capturedWebhook(path, since) {
const base = (process.env.AMMO_TOOLS_API_BASE_URL ?? "https://ammo.tools/api/v1");
const endpoint = process.env.AMMO_TOOLS_ENDPOINT_ID;
const headers = { Authorization: `Bearer ${process.env.AMMO_TOOLS_API_KEY}` };
async function get(route) {
const response = await fetch(`${base}${route}`, {
headers, signal: AbortSignal.timeout(25000),
});
if (!response.ok) throw new Error(`AMMO API: ${response.status}`);
return response.json();
}
const query = new URLSearchParams({ method: "POST", path, since, wait: "20" });
const captures = await get(`/endpoints/${endpoint}/requests?${query}`);
const capture = captures.items.find(item => item.path === path);
if (!capture) throw new Error("No matching webhook arrived within 20s");
const request = await get(`/requests/${capture.id}`);
const payload = JSON.parse(request.body);
if (payload.event !== "payment.succeeded")
throw new Error("Unexpected webhook event");
return payload;
}
// In your test, after triggering the event:
// const payload = await capturedWebhook(path, since);
// expect(payload.amount).toBe(2490);
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
HTTP capture
POST a JSON event to the endpoint's capture URL. Append a path to identify the event or test run.
- Request
POST /payments/succeeded- Content type
application/json
Replace YOUR_WEBHOOK_CAPTURE_URL with the URL from your webhook endpoint.
Use a server action or route handler with the Node.js runtime. Never import SMTP credentials into browser code.
npm install --save-dev @types/nodeconst captureUrl = process.env.AMMO_TOOLS_WEBHOOK_URL;
const response = await fetch(`${captureUrl}/payments/succeeded`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
event: "payment.succeeded",
amount: 2490,
currency: "eur",
}),
signal: AbortSignal.timeout(10000),
});
if (!response.ok) throw new Error(`Capture endpoint: ${response.status}`);
console.log("Webhook sent; inspect it in the endpoint's request list.");
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
Verify a captured webhook
After triggering an event, call this helper with its unique capture path and start timestamp. It returns the parsed JSON after verifying the event type.
- Environment
AMMO_TOOLS_API_KEY- Before the event
- Append a unique path such as
/ci/RUN_ID/to the capture URL and record a UTC ISO timestamp. - Assertion
- Change
payment.succeededto the expected event type.
Replace YOUR_ENDPOINT_ID with the ID from your webhook endpoint.
Use a server action or route handler with the Node.js runtime. Never import SMTP credentials into browser code.
npm install --save-dev @types/node// Record `since = new Date().toISOString()` before triggering the event.
// Send to a unique capture path, e.g. `/ci/${crypto.randomUUID()}/`.
export async function capturedWebhook(path: string, since: string): Promise<Record<string, unknown>> {
const base = (process.env.AMMO_TOOLS_API_BASE_URL ?? "https://ammo.tools/api/v1");
const endpoint = process.env.AMMO_TOOLS_ENDPOINT_ID;
const headers = { Authorization: `Bearer ${process.env.AMMO_TOOLS_API_KEY}` };
async function get<T>(route: string): Promise<T> {
const response = await fetch(`${base}${route}`, {
headers, signal: AbortSignal.timeout(25000),
});
if (!response.ok) throw new Error(`AMMO API: ${response.status}`);
return response.json();
}
const query = new URLSearchParams({ method: "POST", path, since, wait: "20" });
const captures = await get<{ items: { id: string; path: string }[] }>(`/endpoints/${endpoint}/requests?${query}`);
const capture = captures.items.find(item => item.path === path);
if (!capture) throw new Error("No matching webhook arrived within 20s");
const request = await get<{ body: string }>(`/requests/${capture.id}`);
const payload = JSON.parse(request.body);
if (payload.event !== "payment.succeeded")
throw new Error("Unexpected webhook event");
return payload;
}
// In your test, after triggering the event:
// const payload = await capturedWebhook(path, since);
// expect(payload.amount).toBe(2490);
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
HTTP capture
POST a JSON event to the endpoint's capture URL. Append a path to identify the event or test run.
- Request
POST /payments/succeeded- Content type
application/json
Replace YOUR_WEBHOOK_CAPTURE_URL with the URL from your webhook endpoint.
Python 3.10+ · standard library
import os
import json
from urllib.request import Request, urlopen
capture_url = os.environ["AMMO_TOOLS_WEBHOOK_URL"]
request = Request(
capture_url + "/payments/succeeded",
method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps({
"event": "payment.succeeded",
"amount": 2490,
"currency": "eur",
}).encode("utf-8"),
)
with urlopen(request, timeout=10) as response:
print("Webhook sent:", response.status)
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
Verify a captured webhook
After triggering an event, call this helper with its unique capture path and start timestamp. It returns the parsed JSON after verifying the event type.
- Environment
AMMO_TOOLS_API_KEY- Before the event
- Append a unique path such as
/ci/RUN_ID/to the capture URL and record a UTC ISO timestamp. - Assertion
- Change
payment.succeededto the expected event type.
Replace YOUR_ENDPOINT_ID with the ID from your webhook endpoint.
Python 3.10+ · standard library
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
def captured_webhook(path, since):
"""Use a unique capture path and a UTC ISO timestamp from before the event."""
base = os.environ.get("AMMO_TOOLS_API_BASE_URL", "https://ammo.tools/api/v1")
endpoint = os.environ["AMMO_TOOLS_ENDPOINT_ID"]
def get(route):
request = Request(base + route, headers={
"Authorization": "Bearer " + os.environ["AMMO_TOOLS_API_KEY"],
})
with urlopen(request, timeout=25) as response:
return json.load(response)
query = urlencode({"method": "POST", "path": path, "since": since, "wait": 20})
captures = get(f"/endpoints/{endpoint}/requests?{query}")["items"]
capture = next((item for item in captures if item["path"] == path), None)
if capture is None:
raise AssertionError("No matching webhook arrived within 20s")
request = get(f"/requests/{capture['id']}")
payload = json.loads(request["body"])
if payload.get("event") != "payment.succeeded":
raise AssertionError("Unexpected webhook event")
return payload
# In your test, after triggering the event:
# payload = captured_webhook(path, since)
# assert payload["amount"] == 2490
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
HTTP capture
POST a JSON event to the endpoint's capture URL. Append a path to identify the event or test run.
- Request
POST /payments/succeeded- Content type
application/json
Replace YOUR_WEBHOOK_CAPTURE_URL with the URL from your webhook endpoint.
Django · server-side configuration
import os
import json
from urllib.request import Request, urlopen
capture_url = os.environ["AMMO_TOOLS_WEBHOOK_URL"]
request = Request(
capture_url + "/payments/succeeded",
method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps({
"event": "payment.succeeded",
"amount": 2490,
"currency": "eur",
}).encode("utf-8"),
)
with urlopen(request, timeout=10) as response:
print("Webhook sent:", response.status)
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
Verify a captured webhook
After triggering an event, call this helper with its unique capture path and start timestamp. It returns the parsed JSON after verifying the event type.
- Environment
AMMO_TOOLS_API_KEY- Before the event
- Append a unique path such as
/ci/RUN_ID/to the capture URL and record a UTC ISO timestamp. - Assertion
- Change
payment.succeededto the expected event type.
Replace YOUR_ENDPOINT_ID with the ID from your webhook endpoint.
Django · server-side configuration
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
def captured_webhook(path, since):
"""Use a unique capture path and a UTC ISO timestamp from before the event."""
base = os.environ.get("AMMO_TOOLS_API_BASE_URL", "https://ammo.tools/api/v1")
endpoint = os.environ["AMMO_TOOLS_ENDPOINT_ID"]
def get(route):
request = Request(base + route, headers={
"Authorization": "Bearer " + os.environ["AMMO_TOOLS_API_KEY"],
})
with urlopen(request, timeout=25) as response:
return json.load(response)
query = urlencode({"method": "POST", "path": path, "since": since, "wait": 20})
captures = get(f"/endpoints/{endpoint}/requests?{query}")["items"]
capture = next((item for item in captures if item["path"] == path), None)
if capture is None:
raise AssertionError("No matching webhook arrived within 20s")
request = get(f"/requests/{capture['id']}")
payload = json.loads(request["body"])
if payload.get("event") != "payment.succeeded":
raise AssertionError("Unexpected webhook event")
return payload
# In your test, after triggering the event:
# payload = captured_webhook(path, since)
# assert payload["amount"] == 2490
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
HTTP capture
POST a JSON event to the endpoint's capture URL. Append a path to identify the event or test run.
- Request
POST /payments/succeeded- Content type
application/json
Replace YOUR_WEBHOOK_CAPTURE_URL with the URL from your webhook endpoint.
PHP 8.1+ · cURL extension
<?php
$captureUrl = getenv('AMMO_TOOLS_WEBHOOK_URL');
$curl = curl_init($captureUrl . '/payments/succeeded');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'event' => 'payment.succeeded',
'amount' => 2490,
'currency' => 'eur',
], JSON_THROW_ON_ERROR),
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$error = curl_error($curl);
curl_close($curl);
if ($body === false || $status < 200 || $status >= 300) {
throw new RuntimeException("Capture endpoint: $status $error");
}
echo "Webhook sent; inspect it in the endpoint's request list.\n";
Configure your environment
Add these values to your app’s local .env file and replace the placeholders. Make sure your app loads that file before running the example; the code reads environment variables, not the file itself.
AMMO_TOOLS_API_BASE_URL=https://app.ammo.tools/api/v1 AMMO_TOOLS_WEBHOOK_URL=YOUR_WEBHOOK_CAPTURE_URL AMMO_TOOLS_ENDPOINT_ID=YOUR_ENDPOINT_ID AMMO_TOOLS_API_KEY=YOUR_ENVIRONMENT_API_KEY
Verify a captured webhook
After triggering an event, call this helper with its unique capture path and start timestamp. It returns the parsed JSON after verifying the event type.
- Environment
AMMO_TOOLS_API_KEY- Before the event
- Append a unique path such as
/ci/RUN_ID/to the capture URL and record a UTC ISO timestamp. - Assertion
- Change
payment.succeededto the expected event type.
Replace YOUR_ENDPOINT_ID with the ID from your webhook endpoint.
PHP 8.1+ · cURL extension
<?php
// Use a unique path such as '/ci/' . bin2hex(random_bytes(16)) . '/'.
// Record $since = gmdate('Y-m-d\TH:i:s\Z') before triggering the event.
function capturedWebhook(string $path, string $since): array {
$base = (getenv('AMMO_TOOLS_API_BASE_URL') !== false ? getenv('AMMO_TOOLS_API_BASE_URL') : 'https://ammo.tools/api/v1');
$endpoint = getenv('AMMO_TOOLS_ENDPOINT_ID');
$get = function (string $route) use ($base): array {
$curl = curl_init($base . $route);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 25,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AMMO_TOOLS_API_KEY')],
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$error = curl_error($curl);
curl_close($curl);
if ($body === false || $status !== 200) {
throw new RuntimeException("AMMO API: $status $error");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
};
$query = http_build_query(['method' => 'POST', 'path' => $path, 'since' => $since, 'wait' => 20]);
$captures = $get("/endpoints/$endpoint/requests?$query")['items'];
$matching = array_values(array_filter($captures, fn($item) => $item['path'] === $path));
if (!$matching) throw new RuntimeException('No matching webhook arrived within 20s');
$id = $matching[0]['id'];
$request = $get("/requests/$id");
$payload = json_decode($request['body'], true, 512, JSON_THROW_ON_ERROR);
if (($payload['event'] ?? null) !== 'payment.succeeded') {
throw new RuntimeException('Unexpected webhook event');
}
return $payload;
}
// In your test, after triggering the event:
// $payload = capturedWebhook($path, $since);
// self::assertSame(2490, $payload['amount']);
Sign in to fill in your project's connection details. API access is included on every plan.
Webhook API reference
| Route | Use |
|---|---|
GET /api/v1/endpoints | List webhook endpoints and capture URLs |
GET /api/v1/me | Project, workspace, plan and quotas |
GET /api/v1/endpoints/{id}/requests | Filter by method, path prefix, since; wait up to 30s |
GET /api/v1/requests/{id} | Exact request and delivery history |
POST /api/v1/requests/{id}/replay | Replay to a saved target with optional edits |
Pagination and polling
Lists return {"items": […], "next": "public_id or null"}, newest first. Use before to page backward and after for newer captures. Limit is 1–100. A missing or expired cursor returns invalid_cursor. A timed-out wait returns an empty list. Each workspace can hold up to four concurrent waits.
Forward webhooks to localhost
Download the Python client and run it on your machine with AMMO_TOOLS_API_KEY set in the environment.
python3 ammo-listen.py --api https://app.ammo.tools/api/v1 --endpoint whe_… --to http://localhost:3000
Captured paths are appended to the target base URL. New requests only by default; --replay-last N includes recent captures. --interval accepts 1–60 seconds.
Read captures from your coding agent with MCP
Errors
{"error": {"code": "quota_exceeded", "message": "…", "fields": {}}}Cross-environment objects return 404. API keys are environment-scoped; session cookies are not accepted by the API.