Webhook job data feed
Job events delivered to any HTTPS endpoint, signed and idempotent.
Jobo posts a JSON envelope of job events to the URL you configure: job.upserted for every new or changed job matching your filters, and job.expired when a job closes. Requests are signed with an HMAC-SHA256 secret so you can verify they came from Jobo, and every delivery carries a deterministic delivery_id so a repeat can be recognised and dropped. The endpoint only has to answer 2xx quickly; do your processing afterwards. A transformation pipeline can reshape the payload before it is sent, which is what most automation tools want instead of a 20 KB job record.
- Sync
- Incremental, every 15 min
- Deduplication
- Upsert on job ID
- Pipelines
- Supported
- Credentials
- AES-256 at rest
Connection configuration
The fields Jobo collects to reach your Webhook instance.
urlstringrequiredAbsolute HTTPS URL on port 443 that resolves to a public address. Redirects are not followed. Changing it replays the feed to the new endpoint.
http_methodselectPOST (default) or PUT.
batch_sizenumberEvents per request, 1–500 (default 50). Set 1 for one event per request, which is what most no-code catch-hooks expect.
flatten_single_eventbooleanWith batch_size 1, send the event object at the top level instead of inside an events array.
signing_secretstringHMAC secret for X-Jobo-Webhook-Signature. Leave blank and Jobo generates a whsec_ value on save; you can read it back from the feed's settings. Encrypted at rest.
auth_methodselectnone (signature only), bearer, header, or basic — for endpoints that require their own credential on top of the signature.
custom_headersstringExtra headers, one Name: value per line. Host, Content-Type, Content-Length and X-Jobo-* are reserved.
{
"url": "https://hooks.example.com/jobo",
"http_method": "POST",
"batch_size": 50,
"flatten_single_event": false,
"signing_secret": "whsec_••••••••",
"auth_method": "bearer",
"auth_token": "••••••••",
"custom_headers": "X-Tenant: acme"
}How to sync jobs to Webhook
One-time setup. After this, the feed maintains itself.
- 1
Stand up an HTTPS endpoint that answers 2xx fast
Jobo POSTs JSON to your URL and treats any 2xx as delivered. Acknowledge first and process afterwards — the request times out after 30 seconds, and a slow handler is retried like an unreachable one. The URL must be HTTPS on port 443 and resolve to a public address; redirects are not followed, so give the exact final URL (a trailing-slash redirect is a failed delivery). Route case matters: /Jobo and /jobo are different paths on most platforms.
javascript// Next.js route handler — app/api/jobo/route.ts export async function POST(request: Request) { const raw = await request.text(); // verify the signature (step 2), then enqueue `raw` for processing return new Response(null, { status: 202 }); } - 2
Verify the signature
Every request carries X-Jobo-Webhook-Id (the delivery id), X-Jobo-Webhook-Timestamp (unix seconds), and X-Jobo-Webhook-Signature. The signature is v1=<hex> — an HMAC-SHA256 over `${id}.${timestamp}.${rawBody}` using your signing secret. During a secret rotation the header lists two values, current first, so accept any one that matches. Reject timestamps more than five minutes old to bound replay. Also present: X-Jobo-Delivery-Attempt (1–3), X-Jobo-Destination-Id, and User-Agent: Jobo-Webhook/1.0.
javascriptimport { createHmac, timingSafeEqual } from "node:crypto"; export function verifyJobo(headers: Headers, rawBody: string, secret: string) { const id = headers.get("x-jobo-webhook-id") ?? ""; const ts = headers.get("x-jobo-webhook-timestamp") ?? ""; const sigs = (headers.get("x-jobo-webhook-signature") ?? "").split(","); if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const expected = createHmac("sha256", secret) .update(`${id}.${ts}.${rawBody}`) .digest("hex"); return sigs.some((entry) => { const [scheme, hex] = entry.trim().split("="); return scheme === "v1" && hex?.length === expected.length && timingSafeEqual(Buffer.from(hex, "hex"), Buffer.from(expected, "hex")); }); } - 3
Handle the two event types
The envelope has delivery_id, destination_id, created_at, sync_stamp, and an events array. job.upserted carries the full job (or your pipeline's reshaped document) in data — treat it as an upsert keyed on job_id; there is no separate created event. job.expired carries only job_id: mark the job closed. Expiry events are platform-wide, not filtered to your feed, so ignore job_ids you never received rather than treating them as errors.
json{ "delivery_id": "23f51963-5eaa-5c16-a0ba-e60926b7b159", "destination_id": "3a0b…", "created_at": "2026-09-02T07:00:00.0000000Z", "sync_stamp": 1788332400, "events": [ { "event": "job.upserted", "job_id": "9f1c…", "occurred_at": "2026-09-02T06:58:11Z", "data": { "id": "9f1c…", "title": "Senior Data Engineer", "company": "…", "apply_url": "…" } }, { "event": "job.expired", "job_id": "4d7e…", "occurred_at": "2026-09-02T06:59:40Z", "data": { "job_id": "4d7e…" } } ] } - 4
Dedupe on delivery_id
Incremental syncs deliberately overlap by five minutes so a job can never fall between two windows, which means the same job at the same version is delivered more than once. delivery_id is a name-based UUID derived from your destination, the event type, the job, and its version — identical every time — so store the ids you have handled and drop repeats. Zapier dedupes on it natively.
- 5
Send a test event, then launch
Saving the feed sends one signed connection.test event so you can inspect the exact payload and headers in the portal and check your verifier. The first sync delivers everything matching your filters; from then on a sync runs every 15 minutes with only new, changed, and expired jobs.
Common issues
What actually goes wrong when connecting Webhook, and the fix.
- high Every delivery fails with HTTP 404 (for Vercel: 'The page could not be found NOT_FOUND').
- The URL Jobo has no longer exists on your deployment — a renamed or removed route, or a redeploy without it. Jobo posts exactly the URL you saved, verbatim. Fix the route (or update the URL in the feed's settings, which sends a test event), then resume the feed. After three consecutive rejected syncs Jobo pauses the feed and emails you, so a dead endpoint is never hammered every 15 minutes.
- medium Deliveries fail with HTTP 301, 302, or 308.
- Redirects are not followed, on purpose (following one would reach an unvalidated host). Configure the final URL — usually the difference is a trailing slash or http vs https.
- medium Signature verification fails.
- Sign the raw request body bytes, not a re-serialised JSON object, prefixed with the id and timestamp: `${id}.${timestamp}.${body}`. Compare hex lowercase, and accept any v1 entry in the comma-separated header during a rotation.
- low The endpoint receives job.expired events for jobs it never saw.
- Expiry events cover every job that closed on the platform in the window, not just your filtered feed. Ignore unknown job_ids; do not fail the request, or the run is retried.
- low Requests time out or arrive again with X-Jobo-Delivery-Attempt: 2.
- Respond within a few seconds and process asynchronously. 408, 429 and 5xx responses are retried up to three times with backoff (Retry-After is honoured, capped at one minute); other 4xx responses are not retried at all.
- low The URL is rejected with 'Only absolute HTTPS URLs' or 'disallowed port'.
- Only https:// on port 443 to a publicly routable host is accepted; private, loopback, and cloud-metadata addresses are refused. Put a public reverse proxy or a tunnel with a public hostname in front of anything internal.
Best practices
- Acknowledge with 2xx immediately and process from a queue — the 30-second timeout is for the acknowledgement, not your work.
- Verify X-Jobo-Webhook-Signature on every request and reject timestamps older than five minutes.
- Persist handled delivery_ids and drop repeats; overlap is by design.
- Attach a transformation pipeline to send only the fields your automation needs instead of the full job record.
- Set batch_size to 1 with flatten_single_event for Zapier, Make and similar catch-hooks; keep 50–500 for your own service.
- If you rotate the signing secret, keep the previous one configured until every in-flight delivery has been verified.
Other destinations
Start syncing jobs to Webhook
Connect the destination, pick your filters, and let the feed run.