CommerceHook

Consume webhooks by polling

Webhooks assume your system can accept an inbound HTTP request. Plenty cannot: a service behind a corporate firewall, a nightly batch job, a serverless function that would rather pull ten events than expose an endpoint, a CI pipeline that only wants to assert “did the webhook fire”.

The pattern here is to keep the webhook and drop the inbound requirement. Register your CommerceHook endpoint URL as the webhook destination in your platform, and CommerceHook accepts and stores every delivery. Your systems then read them from the events API whenever suits, over an outbound HTTPS request that any firewall already allows. Nothing needs a public address, and a consumer that goes down for an hour just picks up where it left off.

Some webhook gateways sell this as a separate product. Here it is the ordinary API.

The polling loop

Events list newest first, so a poller keeps a high-water mark: the id of the newest event it has processed. Each poll reads the head of the list and works down until it meets the mark.

curl "https://app.commercehook.app/api/events?endpointId={endpoint_id}&limit=100" \
  -H "Authorization: Bearer chk_your_key_here"

The response carries events (up to limit of them) and nextCursor. The loop:

  1. Fetch the first page.
  2. Collect events until you reach your remembered high-water mark id.
  3. If the whole page passes without meeting it, follow nextCursor and keep collecting; you were away long enough for more than a page to arrive.
  4. Process the collected events oldest first, then remember the newest id as the new mark.

On the very first run there is no mark, so decide how much history the consumer should swallow: everything, or only what arrives from now on (remember the current newest id and process nothing).

Fetch any event in full, headers and raw body included, when the preview is not enough:

curl https://app.commercehook.app/api/events/{event_id} \
  -H "Authorization: Bearer chk_your_key_here"

Filter at the source rather than in your consumer when only some events matter. q matches event type and payload text, so &q=order.updated polls just those.

The same loop with the CLI

For scripts and cron jobs the CLI wraps the same API:

commercehook events my-endpoint --json | jq -r '.events[].id'

--search narrows the list the way q does. And when a machine can hold a connection open, commercehook events my-endpoint --tail --json skips polling entirely and streams one JSON line per delivery, which is often the better fit for a long-running worker.

Honest trade-offs

  • Freshness is your interval. Poll every minute and an event can sit for a minute. The dashboard shows deliveries the moment they arrive; a poller sees them on its own schedule. Choose the interval for what the consumer actually needs rather than what feels responsive.
  • Retention bounds how far behind you can fall. Events are stored for 24 hours on Free and 90 days on Pro, so a consumer that stays down longer than the window loses the deliveries that aged out. For a nightly job on Free that leaves headroom of a day; treat anything longer as a reason for Pro.
  • The rate limit is 120 requests per minute per API key. A poll is one request per page, so even aggressive schedules sit far below it. Give each consumer its own key and the limit never couples them.
  • Replays still work. A poller that hits a bad deploy can re-read the same events, and replay can re-send any of them to a real handler once one exists.

Where this fits

  • Behind a firewall: outbound HTTPS to app.commercehook.app is the only network requirement.
  • Batch processing: a cron job that reconciles orders every night polls once, processes, and exits.
  • Serverless: a scheduled function pulls its backlog instead of standing up an always-on endpoint.
  • CI: a test that triggers an action in a sandbox store, then polls until the expected event appears, asserts the whole pipeline end to end. commercehook events --search makes the assertion one line.