Schedule API Calls and Webhooks for Later

Schedule HTTP requests and webhooks for any future time. One API call to schedule, managed delivery with retries and observability. Works with any language — your endpoint handles the logic.

Last updated: March 24, 2026

You need to make an HTTP request at a specific future time. A reminder webhook in 24 hours. A payment retry next Tuesday at 10am. An invitation expiry check in 14 days. A status poll 5 minutes after kicking off an async process.

Posthook does exactly this. One API call to schedule a future HTTP POST or WebSocket delivery to your endpoint — specify when it should fire and what payload to include. Posthook persists the schedule, handles the timing, retries on failure, and gives you visibility into whether it was delivered. Your handler receives the delivery and runs whatever logic the moment requires — calling an external API, sending an email, updating a database. No infrastructure to set up, no workers to run, no cron to maintain.

When you need this

These are the most common reasons developers look for a way to schedule an API call:

  • Reminders and follow-ups — send a trial expiry reminder 3 days before the deadline, nudge an inactive user after 48 hours, trigger a follow-up email if a form was abandoned
  • Expirations and timeouts — expire a pending invitation in 7 days, release a payment hold after 3 business days, revoke a magic link after 15 minutes
  • Payment workflows — retry a failed charge in 24 hours, send an invoice on the billing date, trigger a dunning sequence after a missed payment
  • External integrations — trigger a handler at a scheduled time that calls a partner API, syncs data to a third-party system, or processes a settlement
  • Status checks — poll an async process for completion after a delay, check whether a deployment succeeded 5 minutes after launch
  • Cleanup and maintenance — purge temporary files after a TTL, archive stale records after a grace period

Each of these is a per-event timer — not a recurring cron cadence. The scheduling is dynamic, triggered by something that just happened in your application.

How developers typically solve this

Cron and database polling

Store tasks in a database with a run_at timestamp. A cron job polls every minute for due rows, processes them, and marks them complete.

This works at low volume. It breaks as load grows: the polling query scans every pending row on every tick, timing precision is limited to the cron interval (up to 59 seconds late), multiple instances processing the same batch create race conditions without careful locking, and failures are silent unless you build monitoring from scratch. At tens of thousands of pending records, this becomes a performance and coordination problem.

Cron is a good fit for simple, low-volume recurring tasks where the team already has database infrastructure and ops capacity. It becomes coordination work when every event creates its own timer.

Cloud provider schedulers

AWS EventBridge Scheduler is the most capable — it supports one-time and recurring schedules, configurable retries, and scales to millions of schedules. Google Cloud Scheduler handles recurring HTTP targets with cron expressions. Google Cloud Tasks supports delayed HTTP execution with a 30-day maximum delay.

These are powerful tools, but they come with platform lock-in and infrastructure complexity. EventBridge requires IAM roles, target configurations, and AWS-specific knowledge. Cloud Scheduler and Cloud Tasks are GCP-only. None offer per-schedule observability with attempt history, anomaly detection, or bulk incident response across schedules.

Cloud schedulers are a good fit for teams already deep in a provider’s ecosystem who need to target cloud-native services like Lambda or Cloud Functions.

Queue systems with delay

SQS supports a maximum 15-minute delay — not viable for most scheduling use cases. BullMQ (Redis-backed, Node.js) and Celery (Python) support arbitrary delays, but require Redis or RabbitMQ infrastructure. Memory usage scales linearly with the number of pending jobs. Timing precision depends on polling intervals and worker availability.

Queue systems are a good fit when the team is already running queue infrastructure for job processing and adds scheduling as a secondary concern. They are overhead for scheduling-only needs.

Workflow engines

Inngest, Trigger.dev, and Temporal run your code in their execution runtime. Scheduling is one feature within a broader platform for multi-step workflows, branching logic, and durable execution.

If the scheduled action is one step in a complex workflow, a workflow engine is the right tool. If you need to call a URL at a specific time, a full execution platform adds complexity that the problem does not require.

How Posthook works

The model is straightforward:

  1. Schedule — make an API call with the target path, payload, and when to deliver
  2. Persist — Posthook stores the schedule durably in PostgreSQL with synchronous replication
  3. Deliver — at the scheduled time, Posthook sends an HTTP POST (or WebSocket message) to your endpoint
  4. Retry — if delivery fails, Posthook retries with configurable backoff
  5. Track — every delivery attempt is logged with status, response, and timing

Three scheduling modes

Posthook supports three ways to express “when”:

import Posthook from "@posthook/node";

const posthook = new Posthook("phk_...");

// Exact UTC time
await posthook.hooks.schedule({
  path: "/hooks/payment-retry",
  postAt: "2026-04-01T14:30:00Z",
  data: { paymentId: "pay_abc" },
});

// Relative delay from now
await posthook.hooks.schedule({
  path: "/hooks/send-reminder",
  postIn: "24h",
  data: { userId: "user_123" },
});

// Local timezone with DST handling
await posthook.hooks.schedule({
  path: "/hooks/trial-expiry",
  postAtLocal: "2026-04-01T09:00:00",
  timezone: "America/New_York",
  data: { trialId: "trial_xyz" },
});

SDKs are available for Node.js, Python, and Go. The underlying API is plain HTTP and JSON — any language that can make an HTTP request can schedule a hook without an SDK.

The handler checks state and decides

When the hook fires, your endpoint receives the delivery as an HTTP POST. The handler checks the current state of the resource and decides what to do — act or skip:

export async function POST(req: Request) {
  const { data } = await req.json();
  const payment = await db.payments.findById(data.paymentId);

  // Already succeeded or cancelled — nothing to do
  if (!payment || payment.status !== "failed") {
    return Response.json({ skipped: true });
  }

  await retryPaymentCharge(payment);
  return Response.json({ retried: true });
}

This pattern makes the handler safe to run at any time. If the payment was already retried by another path, the handler skips. If the hook fires twice due to a retry, the second run is a no-op. The handler is the correctness mechanism — not cancellation, not exactly-once delivery.

What you get beyond basic scheduling

A raw HTTP scheduler fires a request at a time. Posthook adds the operational layer that matters when scheduling is part of your production system:

  • Per-delivery observability — inspect every hook’s delivery status, attempt history, response body, and timing from the dashboard or API. No log digging required.
  • Anomaly detection — Posthook tracks failure rates per endpoint against historical baselines and alerts via email, Slack, or webhook when something deviates. You find out about endpoint problems before users do.
  • Bulk incident response — if a downstream outage caused a batch of deliveries to fail, retry all failed hooks in a time range with one API call. Filter by endpoint, time range, or hook IDs.
  • Per-hook retry overrides — critical deliveries (payment retries, time-sensitive expirations) can get a more aggressive retry strategy without changing project defaults. Set retryOverride with custom backoff, jitter, and attempt limits at scheduling time.
  • Timezone-aware schedulingpostAtLocal + timezone schedules in the user’s local time with automatic DST handling. A hook scheduled for 9am fires at 9am year-round, even through spring-forward and fall-back transitions.
  • WebSocket delivery — for endpoints without public URLs (local development, internal services, private networks), Posthook delivers via WebSocket. No tunnels or port forwarding required. Use the CLI for interactive local testing.
  • Sequencesrecurring workflows with calendar-based scheduling, dependency graphs between steps, and config-as-code via posthook.toml. Replaces cron for recurring delivery patterns.

Choosing the right approach

ApproachBest forNot ideal when
PosthookDynamic, per-event scheduling with delivery tracking and failure alertingHigh-throughput queue workloads where volume economics dominate
EventBridge SchedulerAWS-native targets (Lambda, SQS), very high volume within AWSMulti-cloud setups, teams without AWS expertise, per-schedule observability
QStashServerless HTTP messaging within the Upstash ecosystemAnomaly detection, observability depth, timezone-aware scheduling with DST
Cloud TasksDelayed HTTP execution within GCP, queue-based workScheduling beyond 30 days, platform independence
Cron + databaseLow-volume recurring tasks, full infrastructure controlPer-event scheduling at scale, reliability requirements, delivery visibility
BullMQ / CeleryTeams already running Redis or RabbitMQ for job processingScheduling-only needs without existing queue infrastructure
Workflow engineComplex multi-step orchestration with branching and compensationSimple timed HTTP delivery where a full execution platform adds unnecessary complexity

For deeper comparisons, see QStash vs Posthook, EventBridge vs Posthook, and cron vs durable scheduling.

Getting started

Install the SDK and schedule your first hook:

npm install @posthook/node
import Posthook from "@posthook/node";

const posthook = new Posthook("phk_...");

// Schedule a reminder 24 hours from now
const hook = await posthook.hooks.schedule({
  path: "/hooks/follow-up-reminder",
  postIn: "24h",
  data: { userId: "user_123", action: "trial_follow_up" },
});

console.log(hook.id);     // Hook ID for tracking
console.log(hook.postAt); // Resolved delivery time (UTC)

SDKs are available for Node.js, Python, and Go. To see scheduling and delivery working end-to-end, try the live Next.js demo or clone the starter repo. See the quickstart guide for full setup instructions including endpoint configuration and signature verification.

Frequently asked questions

Ready to get started?

Create your free account and start scheduling hooks in minutes. No credit card required.