Recommended Free Tools
Build a webhook API as a small, HTTPS-only POST endpoint that authenticates the sender, records each delivery exactly once, queues the work, and returns a 2XX response promptly. The critical implementation detail is to verify the signature against the original request bytes before parsing the JSON. The Node.js and Express example below uses PostgreSQL to deduplicate deliveries and store queued work durably.
What a webhook API does
A webhook is an HTTP callback: a service sends your application a request when an event occurs, such as an order being paid. Your API receives that event; it does not usually poll the sender for changes. The sender and receiver must agree on the endpoint, event format, authentication scheme, and what counts as a successful acknowledgement.
A webhook endpoint is not just a route that parses JSON. It is an externally reachable boundary that must reject forged or malformed requests, tolerate retries, and avoid losing work when downstream systems are slow or unavailable.
Build the endpoint in this order
- Create a narrow HTTPS POST route. For example, use
POST /webhooks/ordersand subscribe only to the events the application actually handles. GitHub recommends HTTPS and limiting webhook subscriptions to needed events. - Capture the raw body. Keep the exact bytes as received before JSON middleware transforms them. HMAC verification is over the bytes, not a parsed-and-reserialized object.
- Authenticate the delivery. Read the provider’s signature header and calculate the expected signature using the provider’s documented algorithm and secret. Compare signatures with a constant-time comparison.
- Validate the event. Only after signature verification, parse the payload and check the event type, required fields, schema/version, and account or tenant identity.
- Deduplicate durably. Store the provider’s delivery ID under a unique constraint. A retry with an already recorded ID must not repeat side effects.
- Queue business work. Put a durable job or outbox record in storage, then acknowledge the request. Let a worker handle email, billing, network calls, and other potentially slow operations.
- Return a documented 2XX response. GitHub’s current webhook best-practices guidance sets a target of responding within 10 seconds. Acknowledge after the event is safely recorded or queued, not after every downstream action finishes.
Node.js and Express example with PostgreSQL
This generic example uses the illustrative headers X-Signature-256 and X-Delivery-Id. Providers use different header names, signature prefixes, and signing rules; replace them with the exact contract for your sender. The example expects WEBHOOK_SECRET and DATABASE_URL environment variables. Install dependencies with npm install express pg.
#1 Best Overall
Create durable delivery and outbox tables
Run this SQL against the PostgreSQL database used by the endpoint. The unique delivery ID prevents duplicate acceptance; the outbox row records work for a separate worker to claim and process.
CREATE TABLE webhook_deliveries (
delivery_id text PRIMARY KEY,
event_type text NOT NULL,
received_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE webhook_outbox (
id bigserial PRIMARY KEY,
delivery_id text NOT NULL UNIQUE REFERENCES webhook_deliveries(delivery_id),
payload jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
processed_at timestamptz
);
Implement the receiver
import express from "express";
import crypto from "node:crypto";
import pg from "pg";
const { Pool } = pg;
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const secret = process.env.WEBHOOK_SECRET;
if (!secret) throw new Error("WEBHOOK_SECRET is required");
app.post("/webhooks/orders", express.raw({ type: "application/json", limit: "1mb" }), async (req, res) => {
if (!Buffer.isBuffer(req.body)) return res.sendStatus(415);
const supplied = req.get("X-Signature-256") || "";
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(req.body)
.digest("hex");
const suppliedBytes = Buffer.from(supplied, "utf8");
const expectedBytes = Buffer.from(expected, "utf8");
const valid = suppliedBytes.length === expectedBytes.length &&
crypto.timingSafeEqual(suppliedBytes, expectedBytes);
if (!valid) return res.sendStatus(401);
const deliveryId = req.get("X-Delivery-Id");
if (!deliveryId) return res.sendStatus(400);
let event;
try {
event = JSON.parse(req.body.toString("utf8"));
} catch {
return res.sendStatus(400);
}
if (!event || typeof event.type !== "string") return res.sendStatus(400);
const client = await pool.connect();
try {
await client.query("BEGIN");
const inserted = await client.query(
`INSERT INTO webhook_deliveries (delivery_id, event_type)
VALUES ($1, $2) ON CONFLICT (delivery_id) DO NOTHING
RETURNING delivery_id`,
[deliveryId, event.type]
);
if (inserted.rowCount === 1) {
await client.query(
`INSERT INTO webhook_outbox (delivery_id, payload)
VALUES ($1, $2::jsonb)`,
[deliveryId, JSON.stringify(event)]
);
}
await client.query("COMMIT");
return res.sendStatus(202);
} catch (error) {
await client.query("ROLLBACK");
console.error("Webhook persistence failed", { deliveryId, error });
return res.sendStatus(500);
} finally {
client.release();
}
});
app.listen(3000, () => console.log("Webhook API listening on port 3000"));
Run the server behind a TLS-terminating proxy or load balancer so the public endpoint uses HTTPS. Do not add express.json() before this route for the same path; it would consume or transform the request body before the raw-body handler can verify it. The transaction makes saving the delivery and adding its outbox record atomic. In production, run a separate worker that claims unprocessed outbox rows, handles them safely, and marks them processed. If worker processing fails, retry from the outbox rather than asking the sender to redeliver an event already accepted.
Important provider-specific changes
- Signature construction: The sample signs raw bytes with HMAC-SHA-256 and prefixes the hex digest with
sha256=. Use your provider’s documented scheme exactly. GitHub documentsX-Hub-Signature-256as an HMAC-SHA-256 digest of the request body and recommends it over the compatibility SHA-1 header. - Delivery identifiers: The example expects
X-Delivery-Id. GitHub usesX-GitHub-Deliveryand identifies the event withX-GitHub-Event. Map your sender’s stable delivery ID to the database key. - Freshness checks: If the provider signs a timestamp, validate its allowed age as described in that provider’s documentation. Do not require a timestamp header from a provider whose contract does not provide one.
- Event validation: Add an allowlist of supported event types, validate the complete schema, and verify that the account or tenant in the payload is expected. A valid signature proves who sent the bytes; it does not prove your application should perform every action described in them.
Retries, duplicates, ordering, and recovery
Webhook senders commonly retry when they do not receive the expected acknowledgement. This makes duplicate delivery normal, not exceptional. The unique delivery key above makes repeated delivery a no-op at acceptance time. Business operations should also be idempotent: if a worker crashes after performing an action but before marking its job complete, it may run that job again. Use idempotency keys or unique business constraints for the side effect, not only for the incoming HTTP request.
Do not assume events arrive in the order they occurred unless the provider explicitly guarantees ordering. If event order matters, use provider sequence/version data where available, or fetch the authoritative current state from the provider before applying a transition. Keep a replay or dead-letter process for events that repeatedly fail, and reconcile important records against the provider API where appropriate.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →When an endpoint outage is repaired, use the provider’s documented redelivery mechanism for missed deliveries. GitHub recommends redelivering missed deliveries after recovery. A successful HTTP response means your system accepted responsibility for the event; operationally, monitor the queue/outbox until the business work reaches a final state.
Security and operations checklist
- Use HTTPS and keep the shared secret out of URLs, source control, and logs. Store it in a secrets manager and rotate it according to your provider’s supported procedure.
- Generate a high-entropy secret and compare signatures in constant time. GitHub’s validation guidance calls for a random high-entropy secret, UTF-8 handling, HMAC validation, and constant-time comparison.
- Set a reasonable request-body limit and reject unsupported content types. Return 4XX for invalid input, but return a retryable server error if durable persistence fails.
- Log delivery ID, event type, account/tenant, verification result, enqueue result, request latency, and final worker status. Do not log secrets or unnecessary personal data.
- Track acceptance failures, queue age, retry counts, dead-letter volume, and processing latency. Alert on stuck work, not just HTTP endpoint availability.
- Document subscribed event types, schema versions, acknowledgement behavior, ordering assumptions, retry policy, replay steps, and ownership for operational recovery.
Choose the right contract for each provider
Before connecting a provider, compare its implementation contract rather than assuming all webhook APIs behave alike.
Rank #3
| Question | Why it matters | What to confirm |
|---|---|---|
| How is the signature made? | Determines the exact raw bytes, algorithm, secret, header, and encoding to verify. | Algorithm, prefix, timestamp inclusion, character encoding, and secret rotation behavior. |
| What identifies a delivery? | A stable ID is necessary to detect retries without repeating work. | Delivery ID header or payload field; whether it is stable across redelivery. |
| What does success mean? | Defines when the sender stops retrying and how much time the endpoint has. | Accepted status codes, timeout window, and whether response body content matters. |
| How are retries and replay handled? | Outage recovery depends on being able to recover deliveries safely. | Retry schedule, retention, manual redelivery tools, and delivery history. |
| Are ordering or duplicates guaranteed? | Determines how to handle state transitions and worker idempotency. | Ordering guarantees, duplicate behavior, event version fields, and scope by account. |
For example, Stripe endpoints are configured with a URL and enabled-event list, and endpoint scope can be account or Connect-specific. GitHub supplies event/action information and delivery IDs in headers. Implement each provider adapter against its own current documentation rather than building one assumed header format into every route.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Common webhook failures and fixes
- Signature verification fails for apparently identical JSON: The request was likely parsed and serialized before verification, or the wrong header, secret, encoding, or signing formula is in use. Verify the original raw bytes and compare the implementation with the provider’s exact specification.
- Valid delivery gets a 401: Check secret configuration, signature prefix and case, and whether the provider is rotating secrets. Never work around this by skipping signature validation.
- Provider times out or retries repeatedly: The handler is probably doing slow business work before responding. Persist the delivery and job quickly, return 2XX, and move external calls to a worker. GitHub’s stated response target is within 10 seconds.
- Duplicate emails, charges, or fulfillment: Deduplicating HTTP requests alone is insufficient if workers can retry after partial success. Make the side effect idempotent and keep the delivery uniqueness constraint.
- Events disappear after a 2XX: Acknowledging before durable storage risks losing the event on process failure. Commit the delivery and outbox/job record before returning success.
- Unsupported event causes a server error loop: Validate the event type explicitly. Ignore or record-and-acknowledge authenticated events the application intentionally does not handle, following the provider’s contract.
- Payload parse errors: Check the content type and JSON syntax only after authentication. Set an intentional size limit and return a client error for malformed payloads.
Or skip the browser setup
ScreenshotNeo is a separate website screenshot API, not a webhook receiver. If you need clean screenshots of pages your webhook workflow references, its one-call endpoint returns an image or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks/CAPTCHAs, blank pages, failed loads, timeouts, and cache hits are not billed; responses identify the page verdict and billing status in headers. Its MCP server provides screenshot tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Try ScreenshotNeo, or sign up free for 1,000 screenshots a month with no card.
Frequently Asked Questions
Can I test a webhook endpoint on localhost?
Use a provider-supported development forwarding or test-delivery method that can reach your local server; the endpoint still needs to validate the real signature and delivery format.
Should an endpoint return 200 or 202?
Either may be appropriate if the provider treats it as a successful 2XX acknowledgement. Follow that provider’s documented accepted-status behavior.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Can one webhook route handle events from several providers?
It can, but separate provider-specific routes or verification adapters are usually safer because headers, signature rules, event envelopes, and retry contracts differ.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

