Webhooks
Receive new feed jobs as signed HTTP POST requests, hourly, daily or weekly.
How delivery works
A feed in webhook mode posts its new matching jobs to your URL on a schedule.
Each request is one feed.delivery event. It is the only event Bordfeed emits.
Bordfeed checks for due deliveries every hour. A feed is due when at least 90% of its frequency has passed since its last delivery, and its first delivery is due on the first check. If no jobs match since the last delivery, no request is sent.
A delivery carries at most 100 active jobs, newest first. Jobs that match the feed's filters and are new since the last delivery (by the feed's since field, first visible by default) are included. The first delivery has no lower bound. If more than 100 match in one window, the extra jobs are not carried to the next delivery. Use a shorter frequency, or export the feed, if you need every job.
Set up a webhook feed
Set the feed's delivery to webhook with a URL, a secret and a frequency:
{
"mode": "webhook",
"url": "https://example.com/hooks/bordfeed",
"secret": "a-random-string-of-at-least-16-chars",
"frequency": "daily"
}| Field | Rule |
|---|---|
url | Required. Must be HTTPS and must not resolve to a private address. |
secret | Required. At least 16 characters. Used to sign every request. |
frequency | Required. hourly, daily or weekly. |
events | Optional. Only ["feed.delivery"] is accepted. |
The URL is checked when you save it and again, with a DNS lookup, before every delivery.
Request
Bordfeed sends a POST with a JSON body and these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Bordfeed-Signature | Hex HMAC-SHA256 of the raw body |
X-Bordfeed-Feed-Id | The feed's id |
The request times out after 10 seconds. Redirects are followed for up to 5 hops, and only within the original origin over HTTPS.
Payload
{
"event": "feed.delivery",
"feed_id": "0b9f0f4e-6a54-4c63-9a3e-1a3f5c2d7e11",
"feed_name": "Remote writing jobs",
"jobs": [
{
"id": "6c1d2f0a-3b7e-4d11-8f55-0e9a7c4b2d10",
"title": "Senior Editor",
"company": { "id": null, "name": "Example Co", "slug": null },
"apply_url": "https://example.com/careers/senior-editor",
"status": "active"
}
],
"delivered_at": "2026-10-07T09:01:12.000Z"
}The example shows a few fields of each job. Each entry in jobs is the full Job
object from the API Reference, the same shape the feed jobs
endpoint returns. The payload has exactly these top-level fields: event,
feed_id, feed_name, jobs and delivered_at.
Verify the signature
X-Bordfeed-Signature is the lowercase hex HMAC-SHA256 of the raw request body,
keyed with your feed secret. Compute it over the exact bytes you received, not a
re-serialized object.
import { createHmac, timingSafeEqual } from "node:crypto";
function isValid(rawBody, signature, secret) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(signature, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}
// Express: use express.raw({ type: "application/json" }) so req.body is a Buffer.
app.post("/hooks/bordfeed", (req, res) => {
const signature = req.get("X-Bordfeed-Signature") ?? "";
if (!isValid(req.body, signature, process.env.BORDFEED_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
// event.jobs ...
res.sendStatus(200);
});The signature does not include a timestamp. Deduplicate on job id if you need
to guard against replays.
Failures and retries
A delivery succeeds when your endpoint returns a 2xx status. Anything else, a timeout, a blocked URL or a network error is a failure.
- After a failure, the feed's delivery cursor is put back. The same jobs are sent again on the next hourly check, so no jobs are skipped.
- The feed keeps a count of consecutive failures. A success resets it to 0.
- After 5 consecutive failures the feed is disabled and stops delivering until it is enabled again.
Each attempt is recorded as a delivery with its HTTP status or error. A delivery request is not retried within the same check.
Credits
Webhook deliveries on paid plans (Pro, Max and Ultra) are not charged credits. Credits are charged for jobs returned by the Feed API poll and by export.