NewWacht Bench is live — AI-assisted development for Wacht
GuidesWebhook Apps

Deliveries, Replay, and Observability

Operate webhook delivery — filtered triage, bounded replay, and success-rate trends from the delivery hooks.

Deliveries, Replay, and Observability

Once endpoints are registered, the operational job is delivery: finding what failed, replaying it without making things worse, and confirming the success rate recovered. Three read hooks cover the view, and the session hook drives replay. Replay is an async batch job, not a per-row retry — that shapes the whole workflow below.

The view: deliveries, analytics, timeseries

useWebhookDeliveries is the delivery stream, filterable by status, endpoint, event, and time. useWebhookAnalytics returns the rollup (success_rate, failed, total_deliveries, and failed_deliveries_24h). useWebhookTimeseries returns per-bucket points for trend lines.

import {
  useWebhookDeliveries,
  useWebhookAnalytics,
  useWebhookTimeseries,
} from "@wacht/nextjs";

export function WebhookOpsSummary() {
  const deliveries = useWebhookDeliveries({ status: "failed", limit: 25 });
  const analytics = useWebhookAnalytics({ fields: ["success_rate", "failed", "total_deliveries"] });
  const timeseries = useWebhookTimeseries({ interval: "hour" });

  if (deliveries.loading || analytics.loading || timeseries.loading) {
    return <div>Loading webhook operations...</div>;
  }

  return (
    <section>
      <div>Failed deliveries: {deliveries.deliveries.length}</div>
      <div>Success rate: {analytics.analytics?.success_rate ?? 0}%</div>
      <div>Timeseries points: {timeseries.timeseries.length}</div>
    </section>
  );
}

Filter deliveries to status: "failed" before anything else — that's the queue you act on. Each delivery row carries the receiver's HTTP status code, so you can tell a 4xx (customer's endpoint rejected it — they need to fix it) from a 5xx or timeout (transient — replay may just work). failed_deliveries_24h from analytics is the number worth alerting on.

Replay, via the session hook

Replay runs against the management session, not a public hook. It dispatches a batch job and hands back a task_id you poll.

const {
  replayDelivery,
  fetchReplayTasks,
  fetchReplayTaskStatus,
  cancelReplayTask,
} = useWebhookAppSession(ticket);

const replay = await replayDelivery({
  status: "failed",
  start_date: "2026-04-01T00:00:00Z",
  end_date: "2026-04-01T23:59:59Z",
});

const taskId = replay.data.task_id;
if (taskId) {
  const taskStatus = await fetchReplayTaskStatus({ taskId });
  const recentTasks = await fetchReplayTasks({ limit: 20 });

  if (taskStatus.data.status === "running") {
    // surface progress in UI
  }

  // optional incident control
  await cancelReplayTask({ taskId });
}

replayDelivery takes either an explicit list of delivery_ids or a date range with optional status / event_name / endpoint_id filters — the range form is what you use during an incident, scoped to the impacted endpoint and window. It returns a task_id; fetchReplayTaskStatus reports status, total_count, processed, replayed_count, and failed_count as the batch runs. cancelReplayTask stops a job mid-flight — keep it reachable, because a replay over a wide window can hammer a receiver that's still recovering.

Two things to do here that the API won't do for you. Pass an idempotency_key so a replay that's retried doesn't double-dispatch. And scope the date range tight: replaying a week of failures at once is how you turn one customer's outage into a thundering-herd against their freshly-fixed endpoint.

The incident path

An alert fires on failed_deliveries_24h crossing your threshold. Filter the delivery stream to the impacted endpoint and event, and read the receiver's status codes to classify the failure — a wall of 4xx means wait for the customer's fix before replaying, a wall of 5xx/timeout means the receiver was down and replay should land. Once the receiver is confirmed healthy, replay a bounded window and poll the task_id to completion. Then check the next timeseries bucket: success rate climbing back is the signal you're done; flat means the fix didn't take.

What to watch and what to guard

The trend lines worth a dashboard tile: failed deliveries by endpoint over 1h/24h/7d, success-rate by event group (one noisy event can mask an otherwise-healthy app), receiver response-time P95/P99, and replay task volume and failure rate (a rising replay failure rate means replays aren't fixing anything).

The guardrails that keep replay from making incidents worse: idempotent receivers (state it in customer docs — replay re-delivers), bounded replay windows, and an audit record of every replay and cancel with who and why. If Wacht auto-disables an endpoint after sustained failure, alert on that transition — a silently disabled endpoint is a customer who stops getting events without knowing it.

  1. Webhook apps
  2. Custom hook flow implementation
  3. Backend JS SDK
  4. Backend API reference

On this page