Testing
Use the SDK in your test suite to verify webhook integrations end-to-end. Create temporary endpoints, trigger your application, and assert on the captured requests.
Updated Mar 2026
Pattern: test, assert, cleanup
import { WebhooksCC } from "@webhooks-cc/sdk";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
const client = new WebhooksCC({
apiKey: process.env.WHK_API_KEY!,
});
describe("payment webhooks", () => {
let endpoint: Awaited<ReturnType<typeof client.endpoints.create>>;
beforeAll(async () => {
endpoint = await client.endpoints.create({
name: "test-payments",
});
});
afterAll(async () => {
await client.endpoints.delete(endpoint.slug);
});
it("sends a webhook on successful payment", async () => {
// Trigger your application with the endpoint URL
await processPayment({
webhookUrl: endpoint.url,
amount: 4999,
});
// Wait for the webhook to arrive
const request = await client.requests.waitFor(endpoint.slug, {
timeout: 5000,
match: (r) => r.method === "POST",
});
const body = JSON.parse(request.body!);
expect(body.event).toBe("payment.success");
expect(body.amount).toBe(4999);
});
});CI/CD integration
Add your API key as a secret in your CI environment. Example GitHub Actions config:
# .github/workflows/test.yml
env:
WHK_API_KEY: ${{ secrets.WHK_API_KEY }}
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm testUsing waitFor
The SDK includes a built-in waitFor method that polls until a matching request arrives:
// Wait for any request
const req = await client.requests.waitFor(endpoint.slug);
// Wait for a POST with a specific body
const req = await client.requests.waitFor(endpoint.slug, {
timeout: 10000,
pollInterval: 500,
match: (r) => {
if (r.method !== "POST") return false;
const body = JSON.parse(r.body ?? "{}");
return body.event === "order.created";
},
});Testing localhost webhook handlers
Use sendTo to send signed webhooks directly to your local server. No webhooks.cc endpoint needed — the request goes straight to localhost with proper provider signatures.
import { WebhooksCC } from "@webhooks-cc/sdk";
const client = new WebhooksCC({ apiKey: process.env.WHK_API_KEY! });
it("handles Polar subscription webhook", async () => {
const res = await client.sendTo("http://localhost:3000/api/webhooks/polar", {
provider: "standard-webhooks",
secret: process.env.POLAR_WEBHOOK_SECRET!,
body: {
type: "subscription.created",
data: {
id: "sub_test_123",
status: "active",
customer: { email: "[email protected]" },
},
},
});
expect(res.status).toBe(200);
});Works with all providers: standard-webhooks (Polar, Svix, Clerk, Resend), stripe, github, shopify, twilio.
Testing utilities
The SDK provides testing helpers via the @webhooks-cc/sdk/testing subpath export:
import { withEndpoint, captureDuring, assertRequest } from "@webhooks-cc/sdk/testing";
// Auto-cleanup wrapper — endpoint is deleted when callback completes
const result = await withEndpoint(client, async (endpoint) => {
await triggerWebhook(endpoint.url);
return client.requests.waitFor(endpoint.slug, { timeout: "10s" });
});
// Capture requests during an async action
const requests = await captureDuring(
client,
async (endpoint) => {
await triggerMultipleWebhooks(endpoint.url);
},
{ count: 3, timeout: "15s" }
);
// Structured assertion with diff output
const result = assertRequest(request, {
method: "POST",
bodyJson: { event: "payment.success" },
});
expect(result.pass).toBe(true);Tips
- Use unique endpoint names per test run to avoid conflicts in parallel CI
- Always clean up endpoints in
afterAll/afterEach - The free plan supports 50 requests/day — enough for most test suites
Framework examples
Standard Webhooks + Vitest
Polar, Svix, Clerk, Resend with sendTo.
Stripe + Vitest
Payment webhook assertions.
GitHub + Jest
Push event verification.
Polar.sh + Playwright
Subscription lifecycle E2E testing.
Playwright E2E
Browser flow + webhook assertions.