Skip to content

Push Delivery

Use push if you already have a server listening or want jobs delivered to serverless functions. No long-lived worker process needed. Chronos makes the HTTP request to you.

If you’d rather pull jobs from your own process, see Worker Setup.

Terminal window
curl -X POST https://api.chronos.sh/v1/schedules \
-H "Authorization: Bearer chrns_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily report",
"handler": "generate-report",
"cron": "0 9 * * *",
"delivery": {
"type": "push",
"http": {
"url": "https://your-app.com/hooks/chronos",
"method": "POST"
}
},
"payload": { "format": "pdf" }
}'

The delivery.http object supports:

Field Type Required Description
url string Yes Your endpoint. Must be HTTPS in production. No private/localhost IPs.
method string Yes GET, POST, PUT, PATCH, or DELETE
headers Record<string, string> No Custom headers sent with every delivery

Custom headers example:

"http": {
"url": "https://your-app.com/hooks/chronos",
"method": "POST",
"headers": {
"X-App-Secret": "your-shared-secret"
}
}

When a job is due, Chronos makes an HTTP request to your endpoint.

{
"job_id": "a1b2c3d4-...",
"handler": "generate-report",
"execution_id": "e5f6g7h8-...",
"scheduled_for": "2026-01-15T09:00:00.000Z",
"attempt": 1,
"payload": { "format": "pdf" }
}
Field Type Description
job_id string Stable job identifier. Same across retries.
handler string | null The handler name from your schedule. null if not set.
execution_id string Unique to this attempt. Use job_id for idempotency across retries.
scheduled_for string ISO 8601 timestamp of when the job was scheduled.
attempt number Attempt number. 1 on first try, increments on retries.
payload object | null The payload from your schedule or job.

Every delivery includes these system headers:

Header Value Description
Content-Type application/json Always JSON
User-Agent chronos.sh/1.0 Identifies Chronos
X-Chronos-Signature sha256=<hex> HMAC-SHA256 signature for verification
X-Chronos-Delivery-Id <execution_id> Same as execution_id in the body
X-Chronos-Timestamp <unix_seconds> Request timestamp (seconds since epoch)

Your custom headers (from delivery.http.headers) are included but system headers take precedence on conflict.

Chronos does not follow redirects.

A minimal Express handler:

server.ts
import express from 'express';
const app = express();
app.use(express.json());
app.post('/hooks/chronos', async (req, res) => {
const { handler, payload } = req.body;
switch (handler) {
case 'generate-report':
await generateReport(payload.format);
break;
case 'sync-tenant':
await syncTenant(payload.tenantId);
break;
default:
console.warn(`Unknown handler: ${handler}`);
return res.sendStatus(400);
}
res.sendStatus(200);
});
app.listen(3000);

Route by the handler field to dispatch different job types to a single endpoint. Returning 400 for unknown handlers prevents Chronos from recording work as completed when nothing ran. For handlers you expect to deploy soon, return 503 instead so Chronos retries.

Your HTTP response tells Chronos what happened:

Your response Chronos records Retries?
2xx (any success) completed No
4xx (client error) failed No: terminal
5xx (server error) failed Yes: exponential backoff
Timeout (no response within limit) timeout Yes
Network error (connection refused, DNS failure) failed Yes

Every delivery is signed with your account’s signing secret (found in the dashboard under Settings → Signing Key). Verify the X-Chronos-Signature header to confirm requests come from Chronos and haven’t been tampered with.

The SDK provides a Webhook class that handles verification:

import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';
const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
// In your request handler (rawBody = the exact bytes received):
try {
const payload = await wh.verify(rawBody, req.headers);
} catch (err) {
if (err instanceof ChronosWebhookVerificationError) {
return res.sendStatus(401);
}
}

See Signature Verification for how to capture the raw body in Express, key rotation, and replay protection options.

The same delivery config works on one-off jobs:

Terminal window
curl -X POST https://api.chronos.sh/v1/jobs \
-H "Authorization: Bearer chrns_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Send welcome email",
"handler": "send-email",
"delay": { "value": 30, "unit": "minute" },
"delivery": {
"type": "push",
"http": {
"url": "https://your-app.com/hooks/chronos",
"method": "POST"
}
},
"payload": { "userId": "usr_abc123", "template": "welcome" }
}'
  • Check your response time. Default timeout is 30 seconds. If your handler takes longer, increase timeout on the schedule/job or optimize the handler.
  • 4xx is terminal. If you accidentally return 401/403 (e.g., middleware rejecting the request), Chronos won’t retry. Return 503 while you fix auth issues.
  • Correlate with X-Chronos-Delivery-Id. This matches execution_id. Use it to find the delivery in your logs and in the Chronos dashboard.
  • attempt tells you which retry you’re on. Use it to adjust behavior on later attempts (e.g., skip optional steps, use a simpler fallback).