---
title: "Webhook receiver and background-job polling examples"
section: "Developer Platform"
canonical_url: "https://support.buildwithhq.com/api/webhook-receiver-and-background-jobs.html"
reviewed_at: "2026-09-14"
authority: Official
---

# Webhook receiver and background-job polling examples

Run the exact-body webhook receiver and bounded AI rebuild poller published at webhooks.buildwithhq.com.

## In brief

- **Examples:** Dependency-free Node.js
- **Receiver:** Verify, queue, then acknowledge
- **Poller:** Bounded + Retry-After

[`webhooks.buildwithhq.com`](https://webhooks.buildwithhq.com/)  publishes runnable examples for the two async integration patterns developers need first: accepting signed events in their own backend and polling a public background job to terminal state. The reference site does not collect or retain customer webhook payloads.

[Download receiver](https://webhooks.buildwithhq.com/examples/webhook-receiver.mjs) [Download poller](https://webhooks.buildwithhq.com/examples/background-job-poller.mjs) [Download offline tests](https://webhooks.buildwithhq.com/examples/examples.test.mjs)

## 1. Run the receiver locally

```
$env:BWHQ_WEBHOOK_SIGNING_SECRET = "the one-time subscription secret"
$env:BWHQ_WEBHOOK_QUEUE_DIR = "C:\ProgramData\MySaaS\webhook-queue"
node webhook-receiver.mjs
```

Publish the local `/webhooks/buildwithhq` route through your own HTTPS reverse proxy and register that URL as the subscription destination. Keep the signing secret and queue storage in the trusted backend.

## 2. Verify, deduplicate, queue, acknowledge

1. Read no more than the configured body limit as exact bytes.
2. Verify HMAC-SHA256 over `<timestamp>.<raw-body>` before parsing.
3. Reject timestamps outside five minutes and malformed envelopes.
4. Atomically accept the event UUID once in durable storage.
5. Return 202 for a new queued event or 200 for a known event.
6. Process later with an idempotent CRM, billing, identity, or onboarding handler keyed by `event.id`.

The example logs only event ID, type, and version. Replace its filesystem store with a production queue or database when needed, retaining a unique event-ID constraint. The downstream mutation must also be repeat-safe because a worker can fail after applying the effect and before recording completion.

## 3. Poll the public background-job contract

The corrected file example flushes staging files before atomic publication and validates duplicate bytes. Worker passes share a directory lock, and failed handlers move to a failed-event marker after three attempts so later events continue. Monitor `failed/`. After a crash, stop all workers before removing a stale `worker.lock`. To replay a failed event, fix its cause and remove only its failed and attempts markers while workers are stopped. Keep the accepted payload. Use a transactional broker or database when production power-loss durability is required; the local hard-link file store is a reference implementation.

Polling deadlines cover requests and waits. Cancellation interrupts both, and a Retry-After exceeding the remaining budget fails without issuing an early retry.

The current public pollable job is an AI rebuild. Queue it once through `POST /v1/apps/{saasAppId}/ai/rebuilds`, retain `jobId`, then read `GET /v1/apps/{saasAppId}/ai/rebuilds/{jobId}`. Queueing requires `ai.write`; status requires `ai.read`; the caller also needs current tenant account-owner authority.

```
const result = await pollAiRebuild({
  saasAppId: process.env.BWHQ_SAAS_APP_ID,
  credential: process.env.BWHQ_USER_TOKEN,
  jobId: receipt.jobId,
  maximumWaitMs: 10 * 60 * 1000,
});
```

The example stops on `completed`, `failed`, or `cancelled`, sleeps between other states, honors the required whole-second `Retry-After` header after 429, supports cancellation, and enforces a maximum wait. It never automatically repeats the queue POST after an uncertain response.

## 4. Verify without credentials

```
node --test examples.test.mjs
```

The offline tests send signed raw bytes to a loopback receiver, reject a bad signature, prove duplicate acknowledgement and one-time processing, then exercise queued, rate-limited, completed, and failed polling responses.

Note

An asynchronous installed-service invocation returns an invocation ID that is also its durable task ID. Poll GET /v1/apps/{saasAppId}/services/invocations/{invocationId} with appliances.read, or request revision-fenced cooperative cancellation through POST /v1/apps/{saasAppId}/services/invocations/{invocationId}:cancel with appliances.invoke. These are separate from the AI rebuild job contract.

---

Capability review: 2026-09-14. [View the canonical guide](https://support.buildwithhq.com/api/webhook-receiver-and-background-jobs.html).
