A webhook endpoint is the URL on your server that receives webhook requests from another service. You register it with a provider such as Stripe, GitHub or Shopify. When an event happens there, the provider sends an HTTP POST with the event details to your endpoint, and your code processes it and replies with a 2xx status.
https://api.example.com/webhooks/stripe
└────────── host ──────┘└──── path ────┘
How a webhook endpoint works
- You create a route in your app, for example
POST /webhooks/stripe. - You give its full public URL to the provider. That's the webhook URL.
- An event fires. The provider sends a request whose body is the webhook payload, usually JSON, along with headers that include a signature.
- Your endpoint verifies the signature, stores or queues the event, and returns
200 OK. - If the endpoint returns an error or times out, the provider retries later.
Example endpoint
// Express
const raw = express.raw({ type: 'application/json' })
app.post('/webhooks/stripe', raw, (req, res) => {
// reject the request if the signature is invalid
const sig = req.headers['stripe-signature']
const event = verifySignature(req.body, sig)
queue.add(event) // do slow work later
res.sendStatus(200) // acknowledge fast
})
What makes a good webhook endpoint
- Publicly reachable over HTTPS. Providers can't reach
localhostor a private network. Many refuse plain HTTP or self-signed certificates (ERR_CERT_AUTHORITY_INVALID). - Fast. Respond in a few seconds and do the heavy lifting asynchronously. Slow handlers cause 504 timeouts and duplicate retries.
- Verified. Check the webhook signature with your webhook secret before trusting the body. You can test one with the signature verifier.
- Idempotent. The same event can arrive more than once, so deduplicate on the event ID. See idempotency.
- Observable. Log every request so you can see what the provider actually sent and replay it.
Testing a webhook endpoint
To see what a provider sends before writing any code, point it at a free Webhook Bin URL and inspect the captured requests. To test your real handler on localhost, run relay forward --bucket my-app http://localhost:3000/webhooks. You get a permanent public URL, and each webhook is delivered to your machine.
