Webhook vs API: When to Pull and When to Be Pushed

With an API call, your code asks another service for data whenever it wants: that is pull. With a webhook, the service sends an HTTP POST to your URL the moment something happens: that is push. Most integrations use both, a webhook to hear about a change quickly and an API call to fetch the full, current record.

API request vs webhook
API request (pull)Webhook (push)
Who starts itYour codeThe other service
TimingWhen you ask, or on a polling timerRight after the event
You needAn API key and a clientA public HTTPS endpoint
Proving the senderThe provider checks your keyYou check the provider's signature
When things failYou see the error and retryThe provider retries; you must handle duplicates
Best forReading or changing data on demandReacting to events you did not cause

Updated

Polling an API every minute to see if a payment cleared wastes requests and still arrives late. A webhook flips the direction, but it also moves responsibility onto you: your endpoint has to be public, prove each message is genuine, answer fast and cope with the same event arriving twice. This page covers each of those jobs with code you can copy.

HMAC SHA-256 signature checker

Paste a body and a secret to compute the signature a provider would send, then paste the header you received to compare. Everything runs in your browser with the Web Crypto API; nothing is sent anywhere.

A webhook receiver that checks the signature

Node and Express: HMAC SHA-256 over the raw body

express.raw keeps the body as bytes so the signature matches. This header format follows GitHub's sha256= convention; adapt the header name for other providers.

import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.WEBHOOK_SECRET;

app.post("/webhooks/github", express.raw({ type: "application/json" }), (req, res) => {
  const sent = req.get("X-Hub-Signature-256") || "";
  const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
  const a = Buffer.from(sent);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString("utf8"));
  queue.add(event);          // do the slow work later
  res.sendStatus(204);       // answer quickly
});

Handling retries and duplicates

A unique index on the event id makes a second delivery fail the insert, so the work runs once no matter how many times the event arrives.

Record each event id once (PostgreSQL)

ON CONFLICT DO NOTHING returns no row for a duplicate, which your code treats as already handled.

create table processed_events (
  event_id text primary key,
  received_at timestamptz not null default now()
);

-- inside the worker, in the same transaction as the real work:
insert into processed_events (event_id) values ($1)
on conflict (event_id) do nothing
returning event_id;

The polling alternative

Polling is simpler to host but slower and chattier. Keep a cursor so each poll only asks for what is new.

let since = new Date(Date.now() - 60_000).toISOString();

setInterval(async () => {
  const res = await fetch("https://api.example.com/orders?updated_since=" + encodeURIComponent(since), {
    headers: { Authorization: "Bearer " + process.env.API_KEY },
  });
  const { data } = await res.json();
  for (const order of data) handle(order);
  if (data.length) since = data[data.length - 1].updated_at;
}, 60_000);

Pull: calling an API

An API request is a question your code asks: give me order 42, list today's signups, create this customer. You choose when to ask and you get an answer right away, including a clear error if something went wrong.

The cost is that you only learn about changes when you ask. To notice new events you have to poll on a timer, and most polls come back empty.

Push: receiving a webhook

A webhook is the provider calling you. When the event happens, it POSTs a JSON description of it to the URL you registered, usually within seconds.

Because anyone on the internet can POST to that URL, the body is untrusted until its signature checks out. Because networks fail, the same event may arrive more than once or out of order. A good receiver is built for both from day one.

Using both together

A common, sturdy pattern treats the webhook as a doorbell. It tells you which object changed; you then call the API to read that object's current state and act on it. This sidesteps ordering problems and stale payloads, and a daily poll catches anything the doorbell missed.

How it works

  1. Register a URL on your server with the provider and pick the events you care about.
  2. The provider signs each event body with a shared secret and POSTs it to that URL.
  3. Your endpoint recomputes the signature over the raw body, rejects mismatches, and replies 2xx quickly.
  4. A background job does the real work, skipping any event id it has already processed.

Tips

Frequently asked questions

Is a webhook a type of API?

Loosely, yes. It is an HTTP request with a JSON body, just sent in the opposite direction. People sometimes call it a reverse API. The difference that matters is who starts the conversation and who has to be online to receive it.

Should I poll or use webhooks?

Use webhooks when the provider offers them for the events you need and you can host a public HTTPS endpoint. Poll when there is no webhook, when you are behind a firewall, or for a small number of checks where a minute of delay is fine.

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

Most providers send an HMAC SHA-256 of the body in a header, computed with a secret only you and they know. GitHub, for example, uses X-Hub-Signature-256 with a sha256= prefix; Stripe uses Stripe-Signature with a timestamp. Recompute it and compare before trusting anything.

What happens if my endpoint is down?

Most providers retry failed deliveries with growing gaps between attempts, for a period that depends on the provider. Stripe, for instance, keeps retrying live events for up to three days. After that the event is only available through their API or dashboard.

Why should a webhook handler answer before doing the work?

Providers wait only a short time for a reply. If your code sends emails or calls other services first, the delivery can time out and be retried, even though you already acted on it. Queue the job, return 200, and process afterwards.

Related: Stripe webhooks guide, Discord webhook guide, CORS explained, HTTP status codes, API key generator.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap