Relay Start free

Webhooks in, typed events out.

Relay types every payload, retries failed deliveries and replays any event.

curl --json @evt_1042.json https://relay.example.com/in/billing

Free up to 100,000 events a month.

  • {"…Raw webhook
  • invoice.paidTyped event
  • 503 ┄┄Retry, waiting

One stream for every provider

Every webhook lands here typed, with its delivery state beside it.

Four jobs, one stream

What Relay does between your providers and your endpoint.

Relay reads each payload and gives every field a type.

evt_1042Received from billing.example.comInferred by Relay

evt_1042.json

{
  "type": "invoice.paid",
  "id": "evt_1042",
  "amount": 4800,
  "currency": "GBP",
  "paid_at": "2026-10-08T09:42:16Z"
}

invoice-paid.ts

// inferred from evt_1042
export type InvoicePaid = {
  type: "invoice.paid";
  id: string;
  amount: number;
  currency: "GBP";
  paid_at: string;
};

When your endpoint is down, Relay holds evt_1042 and tries again: a 503, a timeout, then 200 OK.

  1. 503Service Unavailable in 2.1 sAttempt 1, 09:42:18
  2. 10.0 sNo answer, timed outAttempt 2, 09:42:48
  3. 200OK in 182 msAttempt 3, 09:43:48

Send any past event to your endpoint again, straight from the stream.

Replay

invoice.paidevt_1042
Event
evt_1042, invoice.paid
Destination
https://example.com/hooks/billing
Sends the stored payload as it arrived
  1. 09:58:14Replayed evt_1042200 OK164 ms
  2. 09:31:02Replayed evt_0987200 OK201 ms
  3. 08:12:40Replayed evt_0961200 OK176 ms
curl --json @evt_1042.json https://example.com/hooks/billing

One address for each provider. Every webhook lands in one stream, whoever sent it.

SourcePosts toEvent types seenLast event
shop.example.comrelay.example.com/in/shoporder.created order.shipped09:42:31
billing.example.comrelay.example.com/in/billinginvoice.paid refund.issued09:42:18
crm.example.comrelay.example.com/in/crmcustomer.updated contact.merged09:41:55
forms.example.comrelay.example.com/in/formsform.submitted09:39:40

All four deliver to https://example.com/hooks

100,000 events a month, free.

36,000 events in a 30-day month

Inside the free 100,000.

Your handler gets a type

Answer 200 and the event is delivered. Anything else, and Relay tries again.

app/hooks/billing/route.tsYour endpoint

import type { InvoicePaid } from "./invoice-paid";

export async function POST(req: Request) {
  const event = (await req.json()) as InvoicePaid;
  console.log(event.id, event.amount, event.currency);
  return new Response(null, { status: 200 });
}
The type file is the one Relay inferred from evt_1042.