CH. 01 A field guide · from an event to your server

What is a webhook?

It's an app calling you back. Instead of asking a service “anything new?” every few seconds, you give it a URL, and it sends you an HTTP request the moment something happens: a payment, a push, an order. Here's how that works, what one looks like on the wire, and where the URL has to live.

Who starts the request
The provider
Usually
POST · JSON
Your URL must be
Public
Fig. 01 — asking vs being toldLive
Polling an API compared with receiving a webhook Top half, polling: your app asks a service "anything new?" again and again, and the service answers "no" each time until, eventually, "yes". Bottom half, a webhook: your app waits; when the event happens, the service sends one request to your app's URL, and your app answers 200 OK. A · POLLING: YOUR APP ASKS YOUR APP every 10 s: GET /orders? since=… THE SERVICE Shop API ANYTHING NEW? NO ANYTHING NEW? NO ANYTHING NEW? YES · ORDER #42 MOST REQUESTS ARE WASTED · NEWS ARRIVES UP TO 10 S LATE B · WEBHOOK: THE SERVICE TELLS YOU THE SERVICE Shop order #42 placed EVENT YOUR APP https://you.app /webhook 200 OK POST /webhook · order.created 200 OK, WITHIN SECONDS ONE REQUEST PER EVENT · ARRIVES IN A FRACTION OF A SECOND
Polling (A) means asking on a timer and mostly hearing “no”. A webhook (B) turns the direction around: the service sends one request (●) to your URL when the event happens, and your app answers 200 OK.

§ 01 The short answer

Webhooks, in one paragraph

Quick answer

A webhook is an HTTP request that a service sends to a URL you choose when an event happens. You register the URL once, for instance https://your.app/webhook in Stripe's dashboard. From then on, every payment, refund or failed charge arrives there as a POST with a JSON body describing it, and your server answers 200 OK.

It's the opposite direction of a normal API call: the service starts the conversation, so your app learns about events right away without asking over and over. That's also why the URL has to be public and always on: the internet has to be able to reach it.

Spec sheet Key facts

Also called
HTTP callback · event notification · “reverse API”
Direction
Service → your server
Transport
HTTPS, almost always POST
Body
JSON, sometimes form-encoded
Proof of origin
A signature header, usually HMAC-SHA256
Expected answer
A 2xx within seconds (Slack 3 s, Shopify 5 s, GitHub 10 s)
If you miss it
Most providers retry; GitHub doesn't. What each status means

§ 02 The life of a webhook

One payment, step by step

A customer pays in your shop. Here's every message that follows, from the card being charged to the confirmation email.

Fig. 02 — A Stripe payment webhookt = 0 ms ·
Sequence of a payment webhook The customer's browser confirms a payment with Stripe. Stripe charges the card, then sends POST /webhook with a payment_intent.succeeded event to your server. Your server checks the signature, answers 200 OK straight away, and afterwards marks the order paid in the database and sends a confirmation email. Timings are illustrative. CUSTOMER STRIPE YOUR SERVER DB · EMAIL confirm payment CARD CHARGED · EVENT CREATED “payment succeeded” POST /webhookpayment_intent.succeeded SIGNATURE OK 200 OK order #42 → paid send receipt STRIPE IS DONE AT 200 OK. THE SLOW WORK HAPPENS AFTER, SO NO TIMEOUT, NO RETRY.
Illustrative timings. Two things to notice: Stripe tells your server about the payment, not just the customer's browser, which might close before it hears back. And your server answers 200 before doing slow work, because the provider only waits a few seconds.

§ 03 On the wire

What a webhook actually looks like

Strip away the vocabulary and a webhook is an ordinary HTTP request. This is the shape of one Stripe event, shortened:

HTTP request · as your server receives it
POST /webhook HTTP/1.1
Host: your.app
Content-Type: application/json; charset=utf-8
Stripe-Signature: t=1791973163,v1=5257a869e7ec…

{
  "id": "evt_1Q8…",
  "object": "event",
  "type": "payment_intent.succeeded",
  "created": 1791973162,
  "data": {
    "object": { "id": "pi_3Q8…", "amount": 2000, "currency": "eur" }
  }
}

Capture a real one in the tester

Legend Each part

POST /webhook
The URL you registered. The path is yours to choose.
Content-Type
How to read the body. JSON for most providers.
Signature header
Proof that it came from the provider and wasn't changed. How to check it.
id
Unique per event. Store it, and ignore an ID you've already handled: retries send the same one again.
type
What happened. You subscribe to the types you care about.
data
The thing it happened to, often a snapshot. Fetch it fresh from the API if you need the latest state.

§ 04 Interactive

Why not just poll? Do the math.

Polling means asking the API on a timer. Move the sliders and compare what it costs with what a webhook costs for the same news.

Polling
Requests a day
8,640
…that find nothing
99.4 %
Average delay
5 s
Webhook
Requests a day
50
…that find nothing
0 %
Average delay
< 1 s

Polling every 10 seconds costs 173 requests for each event you learn about, and many APIs rate-limit long before that.

Polling still has its place: it needs no public URL, and it's how you catch up after downtime. Many apps do both. Webhooks for speed, and an occasional API sweep so a lost delivery never goes unnoticed.

§ 05 Reference

Webhook vs API vs WebSocket vs server-sent events

Four ways for two programs to stay in sync. They differ in who opens the connection, and for how long.

Who starts itConnectionLearns of an eventNeeds a public URLTypical use
API call (polling)Your appOne request, again on a timerAt the next pollNoFetching data, catching up
WebhookThe serviceOne request per eventWithin a second or soYes, yoursServer-to-server events: payments, pushes, orders
WebSocketYour app, then both waysKept openImmediatelyNoChat, live collaboration, games
Server-sent eventsYour app, then the server talksKept open, one wayImmediatelyNoLive feeds and progress in a browser

The webhook is the only one where your side must be reachable. That's what makes it simple for the sender, and it's the part that trips people up when they develop on a laptop. How to test one locally →

§ 06 In the wild

Webhooks you've probably met

Payments

Stripe

payment_intent.succeeded, invoice.paid, customer.subscription.deleted: the source of truth for what happened to the money, even if the customer closed the tab.

Code

GitHub

push, pull_request, issues: how CI servers, chat bots and deploy tools learn that someone pushed code.

Commerce

Shopify

orders/create, products/update: how apps sync stock, print labels or update a spreadsheet when an order comes in.

Chat

Slack

The Events API sends JSON when someone mentions your bot; slash commands arrive as form fields. Slack expects an answer within 3 seconds.

Messaging

Twilio

When someone texts your number, Twilio calls your URL with the message as form fields, and your answer can tell it what to reply.

The other direction

“Incoming webhooks”

Slack and Discord also use the word for a URL they give you: POST to it and a message appears in a channel. Same idea, opposite side.

§ 07 Receiving them

What a webhook URL needs

Sending a webhook is the provider's job. Receiving one well is yours, and it comes down to five things. Each has its chapter.

§ 08 Your own webhook URL

A webhook needs somewhere to land.
Give it a URL that's always on.

One sentence · no dashboard
A Already have a handler?

Ask your coding agent

Open a terminal in your project folder and paste one of these. The agent packages your code, deploys it, and hands you back a permanent HTTPS URL to register with the provider.

$claude "Deploy this webhook handler to cloudish.ai and give me the live URL. Instructions: https://cloudish.ai/skill.md"
$codex "Deploy this webhook handler to cloudish.ai and give me the live URL. Instructions: https://cloudish.ai/skill.md"

Works with Claude Code, Codex, and other coding agents that can run commands.

B Starting from zero?

Have one built and deployed

Chat assistants deploy for you when they can run code with internet access; otherwise they'll give you the exact commands.

§ 09 FAQ

Frequently asked questions

What is a webhook in simple terms?

A webhook is a service calling you back. You give it a URL; when something happens, such as a payment or a new order, it sends an HTTP request to that URL describing the event. Your app reacts right away, without asking over and over.

What is the difference between a webhook and an API?

With an API call, your app asks and the service answers. With a webhook, the service starts the request and your app answers. Most services offer both: webhooks tell you that something happened, and the API lets you fetch details or act on it.

Is a webhook a GET or a POST request?

Almost always POST, with a JSON body describing the event. Some services send form-encoded bodies instead, such as Twilio for incoming messages and Slack for slash commands.

Do webhooks need a public URL?

Yes. The provider sends the request from the internet, so the URL has to be reachable from the internet, usually over HTTPS. A localhost URL only works on your own computer; while developing, use a tunnel or the provider's CLI.

How do I know a webhook really came from the provider?

Check its signature. Providers sign each request with a secret only you and they know, usually an HMAC-SHA256 of the raw body sent in a header. Recompute it on your side and reject requests that don't match. How, for each provider.

What happens if my server is down when a webhook is sent?

It depends on the provider. Stripe retries for up to three days in live mode, Shopify 8 times over 4 hours, and Slack up to 3 times. GitHub doesn't retry automatically; you redeliver from its delivery log.