Skip to main content
Empirical lets you verify your application sends the correct webhooks. It integrates with webhook.site to capture and query webhook payloads, and includes a custom Playwright assertion for use in tests. All test repositories managed by Empirical have this built-in — no additional setup is required.

Usage

Create a webhook URL

Use getWebhookUrl to generate a unique webhook.site URL for your test. Configure your application to send webhooks to this URL.
import { getWebhookUrl } from "@empiricalrun/playwright-utils";

const webhookUrl = await getWebhookUrl();
// e.g. "https://webhook.site/9981f9f4-657a-4ebf-be7c-1915bedd4775"

// Optionally set expiry in seconds (default: 604800 = 7 days)
const webhookUrl = await getWebhookUrl({ expiry: 3600 }); // expires in 1 hour
const webhookUrl = await getWebhookUrl({ expiry: null }); // never expires

Custom assertion

Use the toHaveReceivedWebhook assertion to verify that a webhook was received. Pass any webhook.site URL to expect() — either one generated with getWebhookUrl or an existing URL — and specify content to match against the body.
import { getWebhookUrl } from "@empiricalrun/playwright-utils";
import { expect } from "@empiricalrun/playwright-utils/test";

// Works with a newly generated URL
const webhookUrl = await getWebhookUrl();
await expect(webhookUrl).toHaveReceivedWebhook({
  content: orderId,
});

// Or with an existing webhook.site URL
await expect("https://webhook.site/your-token-id").toHaveReceivedWebhook({
  content: [roomId, "recording.started"],
});
The assertion polls webhook.site until a matching webhook is found or the timeout is reached. When multiple content strings are provided, all of them must be present in the webhook body.

Querying webhooks

Use queryWebhookRequests to fetch matching webhook requests directly. This is useful when you need to inspect the payload for further assertions.
import { getWebhookUrl, queryWebhookRequests } from "@empiricalrun/playwright-utils";

const webhookUrl = await getWebhookUrl();
const requests = await queryWebhookRequests(webhookUrl, [roomId, "recording.started"]);

// Access the parsed payload
const body = JSON.parse(requests[0].content);
expect(body.status).toBe("completed");
Each request in the returned array is a WebhookRequest with the following properties:
PropertyTypeDescription
contentstringRaw request body (parse with JSON.parse for JSON payloads)
methodstringHTTP method (e.g. POST)
headersRecord<string, string[]>Request headers
urlstringFull request URL
created_atstringTimestamp of when the request was received

Assertion options

OptionTypeDefaultDescription
contentstring | string[]RequiredOne or more terms to match against the webhook body. Multiple terms are AND’d together.
timeoutnumber300000Maximum time in milliseconds to wait for the webhook (default: 5 minutes)