prikke

Webhooks

How we call your endpoints, handle failures, and keep things reliable.

How It Works

When a job triggers, Prikke makes an HTTP request to your configured URL. Your endpoint processes the request and returns a response.

  1. Job triggers (cron schedule or one-time)
  2. Prikke sends HTTP request to your URL
  3. Your server processes the request
  4. Return 2xx status for success
  5. We log the result and schedule retries if needed

Request Format

Every webhook request includes these headers:

POST /your-endpoint HTTP/1.1
Host: your-app.com
Content-Type: application/json
User-Agent: Prikke/1.0
X-Prikke-Job-Id: job_abc123
X-Prikke-Execution-Id: exec_xyz789
X-Prikke-Signature: sha256=abcdef...
HeaderDescription
X-Prikke-Job-IdThe ID of the job being executed
X-Prikke-Execution-IdUnique ID for this execution attempt
X-Prikke-SignatureHMAC signature for verifying authenticity

Verifying Signatures

To ensure requests come from Prikke, verify the signature using your webhook secret:

// Node.js example
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return `sha256=${expected}` === signature;
}

// In your handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-prikke-signature'];
  const isValid = verifySignature(
    JSON.stringify(req.body),
    signature,
    process.env.PRIKKE_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the job...
});

Response Handling

Prikke interprets your response status code:

StatusResultAction
2xxSuccessJob marked complete
4xxClient errorJob marked failed (no retry)
5xxServer errorJob retried with backoff
TimeoutTimeoutJob retried with backoff
Tip: Return 4xx for permanent failures (bad data, validation errors) and 5xx for temporary failures (database down, external service unavailable).

Timeouts

Your endpoint has 30 seconds to respond. If no response is received within this time, the execution is marked as a timeout and scheduled for retry.

Long-running tasks: If your job takes longer than 30 seconds, acknowledge the request immediately and process asynchronously. Return 200 to indicate the job was accepted.

Automatic Retries

Retry behavior depends on the job type:

One-time Jobs (run_at)

One-time jobs automatically retry with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry8 hours

After 5 failed attempts, the job is marked as permanently failed and you'll receive an alert (if configured).

Recurring Jobs (cron)

Cron jobs do not retry. If a scheduled run fails, the next scheduled run will execute as normal. This prevents retry storms and duplicate executions.

Why no retries for cron? A minute-interval cron with 5 retries would create chaos - by the time retries finish, hundreds of scheduled runs would have stacked up. The next scheduled run is effectively your retry.

Idempotency

Your webhook handler should be idempotent - safe to call multiple times with the same data. This is important because:

  • Network issues may cause duplicate deliveries
  • Retries will re-send the same request
  • A timeout might occur after your server processed the request

Use the X-Prikke-Execution-Id header to deduplicate:

// Track processed executions
const processed = new Set(); // Use Redis in production

app.post('/webhook', async (req, res) => {
  const executionId = req.headers['x-prikke-execution-id'];

  if (processed.has(executionId)) {
    return res.json({ status: 'already_processed' });
  }

  // Process the job...
  await doWork();

  processed.add(executionId);
  res.json({ status: 'ok' });
});

Custom Headers

You can configure custom headers when creating a job:

curl -X POST https://prikke.whitenoise.no/api/jobs \
  -H "Authorization: Bearer pk_live_xxx" \
  -d '{"name": "My job", "url": "https://myapp.com/webhook", "cron": "0 * * * *", "headers": {"Authorization": "Bearer my-app-token"}}'