Webhook Guides

Webhook Testing Guide

Webhook testing is the workflow of proving that an integration can receive, inspect, route, deliver, retry, and replay real event payloads before production traffic depends on it. A single successful request is useful, but it is not enough to catch the failures that usually happen in production.

A strong webhook test plan covers realistic payloads, source authentication, rejected requests, destination failures, duplicate events, idempotency, retry behavior, and the logs you need when something breaks.

What is webhook testing?

Webhook testing validates the full event path. The sender creates an event, FastHook receives it through a source URL, FastHook records the inbound request, matching connections create routed events, destination delivery attempts run, and your receiver applies the intended side effect exactly once.

The goal is not only to return 200 OK. The goal is to know which stage succeeded, which stage failed, what payload was involved, and whether the event can be safely retried after you fix the receiver.

Webhook testing loop from fixture to FastHook source URL, request history, routed event, destination attempt, assertions, and replay.
A repeatable webhook test follows the same evidence path every time: send a fixture, inspect the captured request, inspect routed events, inspect destination attempts, assert receiver state, then replay only when needed.

What to test before production

  • Happy-path payloads for every event type the receiver supports.
  • Missing optional fields, unknown fields, schema drift, and malformed JSON.
  • Wrong HTTP methods, empty payloads, oversized payloads, and disabled sources.
  • Authentication failures for Basic Auth, API key, or HMAC source auth.
  • Destination 4xx, 5xx, timeout, and rate-limit behavior.
  • Duplicate event ids and idempotency boundaries before side effects are applied.
  • Manual retry and replay after the destination code or credentials are fixed.
Webhook test matrix covering happy path, source rejection, auth failure, schema drift, duplicate events, destination failure, retry, replay, and assertions.
Use a small matrix of expected outcomes. The important part is not the number of tests; it is proving the integration behaves correctly when the provider, payload, route, or destination is imperfect.

FastHook testing workflow

  1. Create a dedicated test source with the same method and auth behavior you expect in production.
  2. Send realistic payload fixtures to the generated FastHook source URL.
  3. Use request history to confirm the inbound payload, headers, query, method, and accepted/rejected status.
  4. Use event history to confirm the connection created destination events for the source.
  5. Use attempt history to inspect response status, response body, latency, and retry triggers.
  6. Fix the receiver, then retry a single failed event before replaying a larger batch.

FastHook API behavior used by these tests

These examples match the current FastHook API implementation. Control API calls use a project API key or session token, tenancy is selected with x-team-id, and the examples use the /v1 prefix for sources, requests, events, and attempts.

  • Source configs accept allowed_http_methods. If the source allows only POST, a GET test returns 405 and is recorded as a rejected request with SOURCE_METHOD_NOT_ALLOWED.
  • Request lists support from, to, source_id, status, verified, cursors, and include=data.
  • Event lists support source_id, connection_id, destination_id, status, from, and to. Event statuses are uppercase, such as SUCCESSFUL and FAILED.
  • Attempt lists support event_id, order_by=created_at, dir, and limit.
  • A failed event can be queued for retry with POST /v1/events/:id/retry. The retry endpoint queues a new delivery attempt and returns a queued replay event id.

Webhook testing examples

Create a test source

Create test source
curl -X POST "https://api.fasthook.io/v1/sources" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Webhook testing source",
    "type": "WEBHOOK",
    "status": "enabled",
    "config": {
      "auth_type": null,
      "allowed_http_methods": ["POST"]
    }
  }'

Send a realistic test event

Send test event
curl -X POST "https://hook-xxxxxx.fasthook.io/" \
  -H "Content-Type: application/json" \
  -H "X-Event-Type: invoice.payment_failed" \
  -H "X-Test-Run: webhook-testing-2026-05-31" \
  -d '{
    "id": "evt_test_retry_2",
    "type": "invoice.payment_failed",
    "attempt": 2,
    "created_at": "2026-05-31T10:00:00Z",
    "data": {
      "invoice_id": "inv_2201",
      "customer_id": "cus_204",
      "amount_due": 4900,
      "currency": "USD"
    }
  }'

Reusable JSON payload fixture

Payload fixture
{
  "id": "evt_test_retry_2",
  "type": "invoice.payment_failed",
  "attempt": 2,
  "created_at": "2026-05-31T10:00:00Z",
  "data": {
    "invoice_id": "inv_2201",
    "customer_id": "cus_204",
    "amount_due": 4900,
    "currency": "USD"
  }
}

Run a negative method test

Method rejection
curl -i -X GET "https://hook-xxxxxx.fasthook.io/"

# If the source only allows POST, FastHook records the request as rejected
# and returns 405 with: { "ok": false, "error": "method_not_allowed" }

Confirm the accepted request was captured

Inspect accepted requests
curl "https://api.fasthook.io/v1/requests?source_id=src_q6z62b6py5o79b&from=now-24h&to=now&status=accepted&limit=20&include=data" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

Confirm rejected requests are visible

Inspect rejected requests
curl "https://api.fasthook.io/v1/requests?source_id=src_q6z62b6py5o79b&from=now-24h&to=now&status=rejected&limit=20&include=data" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

Confirm routed events for the source

Inspect events
curl "https://api.fasthook.io/v1/events?source_id=src_q6z62b6py5o79b&from=now-24h&to=now&limit=20" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

Find failed delivery events

Inspect failed events
curl "https://api.fasthook.io/v1/events?source_id=src_q6z62b6py5o79b&status=FAILED&from=now-24h&to=now&limit=20" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

Inspect destination attempts

Inspect attempts
curl "https://api.fasthook.io/v1/attempts?event_id=evt_01jv8c4n9p3x7r2t6q5m1k0s8b&order_by=created_at&dir=desc&limit=20" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

Retry one failed event

Retry event
curl -X POST "https://api.fasthook.io/v1/events/evt_01jv8c4n9p3x7r2t6q5m1k0s8b/retry" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

Automate a smoke test with Node.js

Node.js smoke test
const apiBase = "https://api.fasthook.io";
const sourceUrl = "https://hook-xxxxxx.fasthook.io/";
const token = process.env.FASTHOOK_API_KEY;
const teamId = process.env.FASTHOOK_TEAM_ID;
const sourceId = "src_q6z62b6py5o79b";

const eventId = `evt_test_${Date.now()}`;

await fetch(sourceUrl, {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-event-type": "invoice.payment_failed"
  },
  body: JSON.stringify({
    id: eventId,
    type: "invoice.payment_failed",
    data: { invoice_id: "inv_2201", amount_due: 4900 }
  })
});

const requests = await fetch(
  `${apiBase}/v1/requests?source_id=${sourceId}&from=now-1h&to=now&limit=20&include=data`,
  {
    headers: {
      authorization: `Bearer ${token}`,
      "x-team-id": teamId
    }
  }
).then((res) => res.json());

const match = requests.models.find((request) => JSON.stringify(request.data ?? "").includes(eventId));
if (!match) throw new Error("Test webhook request was not captured");

Testing checklist

  • Use separate sources for local, staging, and production tests.
  • Keep fixtures for normal payloads, edge payloads, duplicate ids, and provider-specific headers.
  • Assert request capture before checking destination side effects.
  • Confirm failure records before retrying so you know what changed.
  • Verify the receiver is idempotent before replaying any event that can create external side effects.
  • Keep a narrow time window in API queries so test runs are easy to isolate.

Webhook testing FAQ

What is webhook testing?

Webhook testing is the process of sending realistic events to a webhook endpoint and verifying request capture, routing, destination delivery, error handling, retries, duplicates, and downstream side effects before production traffic depends on the integration.

Why is one successful curl request not enough?

A single happy-path request does not test provider retries, malformed payloads, wrong HTTP methods, signature failures, duplicate event ids, destination timeouts, or replay after a receiver fix.

What should I assert in webhook tests?

Assert that the inbound request was accepted or rejected as expected, the payload and headers were captured, matching routed events were created, destination attempts have the expected status, and duplicate events do not apply side effects twice.

How does FastHook help with webhook testing?

FastHook gives you stable source URLs, request history, routed event records, delivery attempt logs, retry controls, and replay so webhook tests can verify each stage instead of relying only on provider dashboard status.

Try FastHook

Create a source, send a fixture, inspect request history, inspect routed events, read destination attempts, and retry only the failed events that need another delivery. This gives your team repeatable webhook tests without rebuilding request logs, replay scripts, and local forwarding for every integration.

Related guides