Receiving jobs
When a job runs, queue-service calls your queue endpoint with a signed JSON body. Your service must verify the signature with the queue signature_secret, then process data.
Request shape
POST /hooks/deliver HTTP/1.1
Content-Type: application/json
x-bq-queue-request-signature: t=1710000000,v1=...{
"timestamp": 1710000000,
"data": {
"invoiceId": 42,
"action": "send"
}
}The content of data depends on how the job was created:
| Source | Typical data |
|---|---|
POST /api/jobs/one-off | Your payload as sent (for example { invoiceId: 42 }) |
POST /api/jobs/schedule | Your payload plus idempotencyKey |
YAML cron_jobs | The cron payload from config (for example { type: 'daily_digest' }) |
Timeout on the worker side is 2 minutes. Return 2xx only when the work succeeded. Anything else is failed and retried per queue config (retryLimit, retryDelay, retryBackoff).
Express handler
import express from 'express';
import { verifySignature } from './verifySignature.js'; // see Request signing
const app = express();
const QUEUE_SECRET = process.env.WEBHOOK_SIGNING_SECRET;
const HEADER = 'x-bq-queue-request-signature';
app.post(
'/hooks/deliver',
express.raw({ type: 'application/json' }),
async (req, res) => {
const raw = req.body.toString('utf8');
const header = req.headers[HEADER];
if (!verifySignature(raw, header, QUEUE_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const parsed = JSON.parse(raw);
const payload = parsed.data ?? {};
// do the work
await deliverWebhook(payload);
return res.status(200).json({ ok: true });
},
);Use express.raw (or equivalent) so verification sees the same bytes the worker signed. Parsing JSON first and re-stringifying will fail verification.
Routing by payload
One endpoint can serve both API jobs and static crons if you branch on fields you control:
const { data } = JSON.parse(raw);
if (data.type === 'daily_digest') {
await runDailyDigest();
} else if (data.action === 'publish') {
await publishPost(data.postId);
} else {
await deliverWebhook(data);
}
return res.status(200).json({ ok: true });Return 5xx (or any non-2xx) if the work failed and should retry. Return 2xx only when it is safe not to run again.
What to put behind the URL
- Prefer private network URLs when both sides live in the same VPC/cluster.
- If the handler is reachable from the public internet, keep TLS at the edge and treat
signature_secretas the only proof the caller is queue-service. - Rotate queue secrets by updating YAML/env and redeploying both sides together.
After success
- Optional
wait_interval_after_success_secondson the queue delays the next job from that queue. - Schedule-API jobs clean up their idempotency row after success.
- One-off idempotency rows remain until delete or replace. Call delete if you want the key reusable without canceling a live job first.