RunLater Docs

Dashboard

1. Trial expiry

When a user starts a trial, schedule the downgrade for when it ends.

// POST /api/schedule
const res = await fetch("https://<worker>/api/schedule", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Cookie": sessionCookie,
  },
  body: JSON.stringify({
    target_url: "https://yourapp.com/api/downgrade",
    execute_at: trialEndDate.toISOString(),
    payload: { user_id: "u_123", plan: "free" },
  }),
});

2. Undo delete window

Soft-delete immediately, schedule hard-delete in 30 minutes. Cancel if the user clicks undo.

// Schedule hard delete 30 min from now
const deleteAt = new Date(Date.now() + 30 * 60 * 1000);

const { id: jobId } = await fetch("https://<worker>/api/schedule", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Cookie": sessionCookie },
  body: JSON.stringify({
    target_url: "https://yourapp.com/api/hard-delete",
    execute_at: deleteAt.toISOString(),
    payload: { record_id: "rec_456" },
  }),
}).then(r => r.json());

// Store jobId so you can show "undo" in your UI
// (RunLater MVP does not support cancellation yet -
//  your hard-delete endpoint should check if the
//  record was restored before deleting)

3. Verify webhook signature (Node.js)

Every RunLater request includes X-RunLater-Timestamp and X-RunLater-Signature headers.

const crypto = require("crypto");

function verifyRunLater(req, secret) {
  const timestamp = req.headers["x-runlater-timestamp"];
  const signature = req.headers["x-runlater-signature"];
  const body = req.rawBody; // exact request body string

  // Reject old timestamps (5 min window)
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return false;
  }

  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + body)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

Signature format

The signature is computed as:

HMAC-SHA256(your_webhook_secret, timestamp + "." + raw_request_body)

Your webhook secret is available on the Settings page. Always use constant-time comparison. Reject timestamps older than 5 minutes.