Self-hosted from my home lab. It's offline right now, so you're viewing a snapshot. Check back soon for the live site.
All posts
Tech Basics

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.

  1. See a webhook arrive. In one terminal, listen on a port:

    nc -l 9000
    

    In 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. curl then reports Empty reply from server, because nc only prints and never answers. A real receiver replies 200 OK quickly, or the sender treats it as failed and retries.

  2. 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-Sha256 header. Work it out yourself and compare:

    body='{"id":1001,"event":"order.created"}'
    printf '%s' "$body" | openssl dgst -sha256 -hmac 'my-webhook-secret' -binary | base64
    

    It 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's crypto.timingSafeEqual.

  3. Open a WebSocket. In one terminal, start a WebSocket server:

    npx wscat -l 9001
    

    In a second terminal, connect to it:

    npx wscat -c ws://localhost:9001
    

    It 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. Press Ctrl+C in 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 200 fast 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 wscat through npx, and its crypto module checks signatures in a real app
  • Shopify webhooks: an example of signed webhooks with retries
Was this useful?
#api#basics#webhooks#websockets

Comments

Comments and votes open again when the site is back online.