A webhook URL is the public HTTPS address that a service sends webhook requests to. You register it with a provider such as Stripe, GitHub or Shopify, and whenever a subscribed event happens, the provider sends an HTTP POST with the event data to that address. The code that handles those requests is the webhook endpoint.
https://api.example.com/webhooks/stripe
https://hooks.slack.com/services/T0000000/B0000000/XXXXXXXXXXXXXXXXXXXXXXXX
https://discord.com/api/webhooks/123456789012345678/abcDEF...
The first is a URL you host to receive events. The other two are generated by Slack and Discord so that you can send messages into them.
Two kinds of webhook URL
People say "webhook URL" for two opposite things, and it helps to know which one a guide means.
| Receiving webhook URL | Incoming webhook URL | |
|---|---|---|
| Who hosts it | You | The destination app (Slack, Discord, Teams) |
| Who sends to it | A provider like Stripe or GitHub | You, your script or another service |
| What it's for | Being notified of events | Posting a message into a channel |
| How you secure it | Signature verification | Keep the URL secret, since it is the credential |
How to get a webhook URL
To receive webhooks from a provider:
- Create a route in your app that accepts
POSTrequests, for example/webhooks/stripe. - Deploy it somewhere publicly reachable over HTTPS. The full address, including the path, is your webhook URL.
- Paste it into the provider's webhook settings and pick the events you want.
- Copy the webhook secret the provider shows you, and use it to verify each request.
To test before you have a server: open Webhook Bin and you get a public URL straight away. Point the provider at it and you'll see every request it sends, with headers and the full payload.
To receive webhooks on your own machine: providers can't reach localhost, so you need a public URL that forwards to it. With the relay CLI:
relay forward --bucket my-app http://localhost:3000/webhooks
The command prints a permanent public URL. Give that to the provider, and every request is delivered to your local server. See receiving webhooks on localhost and webhook forwarding.
To post into Slack or Discord: the app generates the URL for you. Follow the step-by-step guides for getting a Slack webhook URL and getting a Discord webhook URL.
What a good webhook URL looks like
- HTTPS with a valid certificate. Many providers refuse plain HTTP, and self-signed certificates fail with errors like
ERR_CERT_AUTHORITY_INVALID. - Stable. If the URL changes every time you restart a tool, you have to update the provider's settings each time and you lose events sent in between.
- One path per provider.
/webhooks/stripeand/webhooks/githubkeep signature checks and parsing separate. - No secrets in the query string. Query strings end up in logs. Use a signature instead.
Common mistakes
- Using
localhostor a private IP. The provider's servers can't reach it, and deliveries fail or time out. - A temporary tunnel URL that changes. Free tunnel tools often give a new random hostname on each restart, so the URL in the provider's settings quietly goes stale.
- A redirect. Many senders don't follow
301or308redirects forPOST, so a missing trailing slash or an HTTP-to-HTTPS redirect can break delivery. Register the final URL. - Pointing at the wrong environment. Keep separate URLs and secrets for production, staging and local development.
- Leaking an incoming webhook URL. A Slack or Discord webhook URL committed to a public repository lets anyone post into your channel. Rotate it if that happens.
If you need one URL that several services receive, or a URL in front of an endpoint that is sometimes down, put a webhook gateway in front: it accepts the request, stores it, and fans it out with retries.
