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:

FieldTypeRequiredDescription
urlstringYesYour endpoint. Must be HTTPS in production. No private/localhost IPs.
methodstringYesGET, POST, PUT, PATCH, or DELETE
headersRecord<string, string>NoCustom 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" }
}
FieldTypeDescription
job_idstringStable job identifier. Same across retries.
handlerstring | nullThe handler name from your schedule. null if not set.
execution_idstringUnique to this attempt. Use job_id for idempotency across retries.
scheduled_forstringISO 8601 timestamp of when the job was scheduled.
attemptnumberAttempt number. 1 on first try, increments on retries.
payloadobject | nullThe payload from your schedule or job.

Every delivery includes these system headers:

HeaderValueDescription
Content-Typeapplication/jsonAlways JSON
User-Agentchronos.sh/1.0Identifies Chronos
X-Chronos-Signaturesha256=<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 responseChronos recordsRetries?
2xx (any success)completedNo
4xx (client error)failedNo: terminal
5xx (server error)failedYes: exponential backoff
Timeout (no response within limit)timeoutYes
Network error (connection refused, DNS failure)failedYes

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.

See Signature Verification for a full implementation with timestamp freshness, key rotation, and Express raw body setup.

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).