Webhook vs WebSocket: What's the Difference?
Both push data to you, but in very different ways: one HTTP request per event, or one connection that stays open.
Both names start with "web" and both send data without you asking every second, so people mix them up all the time. Here's the difference in five minutes, with two terminal demos you can run on your own machine.
TL;DR
- A webhook is one HTTP POST from one server to another, sent when something happens. Then the connection is gone.
- A WebSocket is one connection between a client and a server that stays open, and both sides can send on it at any time.
- Use webhooks to let your backend know about events in another system (Shopify, Stripe, Slack). Use WebSockets for live screens (chat, dashboards, order tracking).
- Always verify a webhook's HMAC signature, and make the handler safe to run twice.
The problem
Mixing the two up leads to designs that fight the tool. Common ones:
- A frontend that calls an API every few seconds to check whether an order has changed, when the backend already gets a webhook the moment it does.
- A plan to "send the webhook to the browser". A webhook needs a public URL that's always listening, and a browser tab is neither.
- A WebSocket server kept open to another company's system just to hear about the odd event, when that system already offers webhooks and retries them for you.
Why it happened
The two get confused because both are "push": you don't keep asking for updates, the data comes to you. The difference is how it gets there.
A webhook is like a delivery notification text. Something happens (your package arrives), the courier sends you one message, and that's it. If your phone is off, the courier tries again later.
A WebSocket is like a phone call. You open the line once, and both sides can talk back and forth until someone hangs up. If the call drops, you have to call back.
| Webhook | WebSocket | |
|---|---|---|
| Direction | One way (sender to your server) | Two way |
| Connection | New HTTP request per event | One long-lived connection |
| Typical use | "Order created", "payment received" | Chat, live dashboards, games |
| Who receives | A URL on your server | A browser or app client |
| If you're offline | The sender retries later | Messages are lost until you reconnect |
How to choose
Ask who needs to hear about the event. If it's your server, and the event happens in someone else's system, use a webhook: the other system calls you, signs the request and retries if you miss it. If it's a person looking at a screen, use a WebSocket so the page updates without a reload.
Many apps use both: a webhook tells the backend an order shipped, and the backend pushes that over a WebSocket to the customer's open order page.
Steps
These run locally on macOS or Linux and need nothing beyond curl, nc and Node.js.
See a webhook arrive. In one terminal, listen on a port:
nc -l 9000In a second terminal, send the kind of POST a store would send when an order is created:
curl -X POST http://localhost:9000/webhooks/orders \ -H 'Content-Type: application/json' \ -d '{"id":1001,"event":"order.created"}'It worked if the first terminal prints the whole request:
POST /webhooks/orders HTTP/1.1, the headers, then the JSON body. That's all a webhook is.curlthen reportsEmpty reply from server, becausenconly prints and never answers. A real receiver replies200 OKquickly, or the sender treats it as failed and retries.Check a webhook signature. Anyone can POST to a public URL, so senders sign the body with a secret you share. Shopify, for example, sends a base64 HMAC-SHA256 of the raw body in the
X-Shopify-Hmac-Sha256header. Work it out yourself and compare:body='{"id":1001,"event":"order.created"}' printf '%s' "$body" | openssl dgst -sha256 -hmac 'my-webhook-secret' -binary | base64It worked if you get
+08TVJHX2tJuO/fMHuMYx5987g5nb4cmeqzjEatT4Ck=. Change one character of the body and the result changes completely, which is how you spot a faked or altered request. In your app, hash the raw body exactly as it arrived (not re-encoded JSON) and compare with a constant-time check such as Node'scrypto.timingSafeEqual.Open a WebSocket. In one terminal, start a WebSocket server:
npx wscat -l 9001In a second terminal, connect to it:
npx wscat -c ws://localhost:9001It worked if the client shows
Connected. Type a line in either terminal and press Enter: it appears in the other one. That's the difference from step 1. The connection stays open and either side can send at any time. PressCtrl+Cin the server terminal and the client is cut off, which is why real WebSocket clients need reconnect logic.
How it works
A webhook: the sender makes a fresh HTTP request for every event and expects a quick 200.
sequenceDiagram
participant Store as Store (sender)
participant API as Your server
participant Queue as Your job queue
Store->>API: POST /webhooks/orders (body + HMAC header)
API->>API: verify HMAC on the raw body
API->>Queue: save event, process later
API-->>Store: 200 OK
Note over Store,API: no 200 in time? The store retries,<br/>so the same event can arrive twice
A WebSocket: one handshake, then both sides send on the same open connection.
sequenceDiagram
participant Browser
participant Server
Browser->>Server: GET /live (Upgrade: websocket)
Server-->>Browser: 101 Switching Protocols
Server-->>Browser: order #1001 packed
Browser->>Server: typing...
Server-->>Browser: order #1001 shipped
Note over Browser,Server: connection drops? The browser<br/>reconnects and asks what it missed
How to avoid it next time
- Pick by who's listening: a server that needs to know something happened gets a webhook, a person watching a screen gets a WebSocket.
- Verify every webhook signature before you trust the body.
- Reply
200fast and do the slow work in a background job, so the sender doesn't time out and retry. - Store each event's ID and skip ones you've already handled. Webhooks can arrive twice or out of order.
- Give WebSocket clients reconnect logic with a back-off, and a way to catch up on what they missed. Mobile networks drop connections often.
Tools used
- curl: sends the test webhook
- netcat (
nc): listens on a port and prints whatever arrives (built into macOS) - OpenSSL: works out the HMAC signature
- wscat: a WebSocket server and client in the terminal
- Node.js: runs
wscatthroughnpx, and itscryptomodule checks signatures in a real app - Shopify webhooks: an example of signed webhooks with retries
Comments
Comments and votes open again when the site is back online.