Schedule when the trial starts
One POST when the trial begins schedules the reminder for 3 days before it ends. Store the returned job id next to the user.
Trial reminders
The hard part of a trial reminder is not sending it - it is not sending it after the user already paid. Webhook Scheduler schedules the reminder for later and lets you cancel it with one API call the instant the user converts, so nobody gets a "your trial is ending" email an hour after they upgraded.
curl https://webhookscheduler.com/api/v1/schedule \
-H "Authorization: Bearer wh_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/hooks/trial-reminder",
"runAt": "2026-07-12T09:00:00.000Z",
"body": { "userId": "usr_4821" },
"idempotencyKey": "trial-reminder-usr_4821"
}'One POST when the trial begins schedules the reminder for 3 days before it ends. Store the returned job id next to the user.
One call cancels the pending reminder the moment the user upgrades. No cron to clean up, no stale reminder to apologize for.
Every attempt is logged with HTTP status, response body, latency, and retries, so a missed reminder is a record you can read, not a mystery.
The usual build is a cron job that runs every few minutes, scans a reminders table, and sends whatever is due. It works until you need to not send one. Now you are also writing the code to delete the row on upgrade, handle the race where the cron fires between upgrade and cleanup, and explain to a customer why they got a trial-ending email after paying.
Scheduling the HTTP call itself removes the table, the polling, and the cleanup. You schedule one job and cancel one job. That is the whole lifecycle.
Schedule the reminder when the trial starts (the code block above), keep the returned job id on the user, then cancel it if they upgrade first:
# When the user upgrades before the reminder fires: curl -X POST https://webhookscheduler.com/api/v1/jobs/job_xxx/cancel \ -H "Authorization: Bearer wh_live_xxx"
The idempotencyKey makes the schedule call safe to run twice - a retried request or a double-fired signup will not create a duplicate reminder. Both schedule and cancel are in the public API.
Every delivery is signed with a Webhook-Signature header (HMAC-SHA256, timestamped) using your workspace secret, so your receiver can confirm the reminder is authentic before it sends an email. Verification examples are in the API docs and the SDK ships a verifySignature helper.
The same schedule-then-cancel pattern covers most lifecycle webhooks: dunning and failed-payment follow-ups, delayed onboarding nudges, and win-back sequences. If you are weighing a queue instead, see how this compares with Upstash QStash and with cron-based scheduling.
You can fire a real scheduled delivery from the playground in about ten seconds without an account, then watch the delivery result come back live. The free plan schedules real reminders up to 7 days ahead; longer trials need a paid plan.
Yes. A single POST to /api/v1/jobs/{id}/cancel drops any pending reminder before it fires. This is the core reason to schedule the webhook instead of running a cron.
The delivery retries automatically with exponential backoff, and every attempt is logged with its HTTP status, response body, and latency so you can see exactly what happened.
Pass an idempotencyKey (for example trial-reminder-<userId>) on the schedule call. Repeat calls with the same key return the same job instead of creating a new one.
The free plan schedules up to 7 days ahead, which covers short trials. Longer horizons (up to 365 days) are available on paid plans.
Start with the free plan, test a real delivery, then upgrade when the workflow becomes production critical.
Test a reminder webhook