SOLENOID.SYSTEMS [...]
SYS ENG PHI PRICING
API
├─ CATCH ├─ GATE ├─ KEY ├─ LATCH ├─ METER ├─ PULSE ├─ RELAY └─ WITNESS

Schedule Webhooks Without a Job Queue in 5 Minutes

#webhooks #serverless #relay #tutorial

You need to send a payment reminder in 3 days. Or a trial expiration notice in 7 days. Or a scheduled notification your user configured.

The standard approach: Redis, a job queue library, worker processes, monitoring dashboards, and manual intervention when jobs get stuck.

For a single delayed HTTP call.

The 5-Minute Version

Relay handles webhook scheduling with one API call.

Step 1: Schedule a webhook

curl -X POST https://api.solenoid.systems/v1/relay \
  -H "Authorization: Bearer sm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "target_url": "https://your-app.com/webhooks",
    "method": "POST",
    "delay": "3d",
    "payload": {
      "event": "payment_reminder",
      "user_id": "user_123",
      "amount_due": 99
    }
  }'

Response:

{
  "relay_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "scheduled_for": 1706553456789,
  "status": "scheduled"
}

That’s it. In 3 days, your webhook fires automatically.

Step 2: It fires automatically

When the delay expires, Relay POSTs your payload to the target URL:

POST /webhooks HTTP/1.1
Host: your-app.com
Content-Type: application/json
X-Solenoid-Signature: abc123def456...
X-Solenoid-Timestamp: 1706553456789

{
  "event": "payment_reminder",
  "user_id": "user_123",
  "amount_due": 99
}

Step 3: Automatic retries

If your endpoint returns 5xx or has network issues, Relay retries up to 3 times with exponential backoff. 2xx means success. 4xx means permanent failure, no retry.

No queue monitoring. No stuck job debugging. Just scheduled delivery with built-in reliability.

Handling the Edge Cases

Dynamic delays: Use TypeScript to compute delays from user input:

async function scheduleTrialExpiration(userId: string, planDays: number) {
  const delay = `${planDays}d`

  const response = await fetch('https://api.solenoid.systems/v1/relay', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sm_your_api_key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      target_url: 'https://your-app.com/webhooks/trial-expiring',
      method: 'POST',
      delay,
      payload: { user_id: userId, plan_days: planDays }
    })
  })

  const { relay_id } = await response.json()

  // Store relay_id in your database for tracking
  await db.users.update(userId, { trial_expiration_relay_id: relay_id })
}

Cancellation: User upgrades before their trial expires? Just don’t handle the webhook. There’s no cancel endpoint because webhooks that fail with 4xx aren’t retried.

Return 400 Bad Request from your webhook handler and Relay stops retrying.

Custom headers: Include authentication or routing metadata:

curl -X POST https://api.solenoid.systems/v1/relay \
  -H "Authorization: Bearer sm_your_api_key" \
  -d '{
    "target_url": "https://your-app.com/webhooks",
    "method": "POST",
    "delay": "5m",
    "payload": {"event": "reminder"},
    "headers": {
      "X-Event-Type": "reminder",
      "X-Internal-Auth": "your_webhook_secret"
    }
  }'

Signature Verification

Relay signs every webhook. Verify it before processing:

import crypto from 'crypto'

function verifyRelay(body: string, signature: string, timestamp: string) {
  const signed = timestamp + body
  const computed = crypto
    .createHmac('sha256', process.env.SOLENOID_API_KEY)
    .update(signed)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computed)
  )
}

// In your webhook handler
const signature = req.headers['x-solenoid-signature']
const timestamp = req.headers['x-solenoid-timestamp']
const body = JSON.stringify(req.body)

if (!verifyRelay(body, signature, timestamp)) {
  return res.status(401).json({ error: 'Invalid signature' })
}

// Process webhook...

Real-World Patterns

Trial expiration workflow:

// Day 0: User signs up
await scheduleTrialExpiration(userId, 14)

// Day 11: Send "3 days left" reminder
await fetch('https://api.solenoid.systems/v1/relay', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer sm_your_api_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    target_url: 'https://your-app.com/webhooks/trial-reminder',
    method: 'POST',
    delay: '11d',
    payload: { user_id: userId, days_remaining: 3 }
  })
})

// Your webhook handler
app.post('/webhooks/trial-reminder', async (req, res) => {
  const { user_id, days_remaining } = req.body

  const user = await db.users.findById(user_id)
  if (user.plan === 'free') {
    await sendEmail(user.email, `Your trial expires in ${days_remaining} days`)
  }

  res.json({ received: true })
})

Payment reminders:

async function schedulePaymentReminder(invoiceId: string, dueDate: Date) {
  const now = Date.now()
  const due = dueDate.getTime()
  const delayMs = due - now
  const delayDays = Math.floor(delayMs / (1000 * 60 * 60 * 24))

  await fetch('https://api.solenoid.systems/v1/relay', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer sm_your_api_key', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      target_url: 'https://your-app.com/webhooks/payment-due',
      method: 'POST',
      delay: `${delayDays}d`,
      payload: { invoice_id: invoiceId }
    })
  })
}

Why Not Build This Yourself?

You could build webhook scheduling. Store jobs in Postgres with a scheduled_for timestamp. Poll every minute. Send the ones that are due. Handle retries with exponential backoff.

But that’s infrastructure. Polling queries. Connection pooling. Clock skew handling. Retry state machines. Job deduplication. Monitoring dashboards.

When your scheduler crashes at 2 AM, your scheduled webhooks don’t fire. Users don’t get trial expiration notices. Payment reminders vanish.

Relay runs on Cloudflare Workers with Durable Objects for reliable scheduling. Global edge deployment. Automatic retries. No servers to monitor.

You schedule webhooks. Cloudflare ensures they fire.

Try It Now

Free tier: 10,000 calls to prototype. Pro tier ($49.99/mo) and above: unlimited Relay usage.

# Schedule a webhook 5 minutes from now
curl -X POST https://api.solenoid.systems/v1/relay \
  -H "Authorization: Bearer sm_your_api_key" \
  -d '{
    "target_url": "https://webhook.site/your-unique-id",
    "method": "POST",
    "delay": "5m",
    "payload": {"test": "hello"}
  }'

Visit webhook.site, grab a test URL, and watch your webhook arrive exactly 5 minutes later.

Your job queue replacement, in one API call.