Skip to main content
Start broad, then narrow by stable IDs. The goal is to gather enough evidence for an audit trail while making as few queries as possible.

Start with health

When the question is “is this forwarder healthy enough to continue?”, begin with health.
repost health \
  --bucket stripe-prod \
  --forwarder prod-api \
  --window 30m \
  --fail-on 'success_rate < 0.99 || dlq_depth > 0' \
  --json
{
  "schema": "repost.health/v1",
  "data": {
    "bucket": { "id": "bkt_01HV9S", "slug": "stripe-prod", "name": "Stripe production" },
    "window": "30m",
    "since": "2026-06-19T08:00:00Z",
    "until": "2026-06-19T08:30:00Z",
    "forwarders": [
      {
        "forwarder_id": "fwd_01HV9S",
        "forwarder_name": "prod-api",
        "success_rate": 0.9722,
        "failed": 4,
        "pending_retries": 2,
        "dlq_depth": 1,
        "p95_latency_ms": 1840.25,
        "last_delivery_at": "2026-06-19T08:29:51Z"
      }
    ]
  }
}
--fail-on turns a threshold into a process failure (partial_failure, exit 8) so CI and agents do not parse text. It accepts success_rate and dlq_depth with comparisons and ||. Here failed, pending_retries, and dlq_depth are non-zero, so continue into inspection.

Search events within bounds

Search only the bucket and time range the workflow needs.
repost events search 'method:POST AND path:/stripe/*' \
  --bucket stripe-prod \
  --since 30m \
  --limit 20 \
  --json
Page older results with --cursor — it is forward-only and bound to this exact query and window. Don’t widen --limit to dodge paging; narrow the query. Full query grammar and cursor rules are in Search & filters.

Inspect an event

Move from search results to a single event by ID.
repost events get evt_01JZ8V1 --json
repost events diff evt_01JZ8V1 evt_01JZ8UZ --json
{
  "schema": "repost.events.get/v1",
  "data": {
    "event": {
      "id": "evt_01JZ8V1",
      "bucket_id": "bkt_01HV9S",
      "method": "POST",
      "path": "/stripe/webhook",
      "received_at": "2026-06-19T08:31:22Z",
      "response_status": 500,
      "request": {
        "headers": {
          "stripe-signature": ["t=1718061000,v1=[redacted:hmac:9f2a1c4d8b07]"],
          "content-type": ["application/json"]
        },
        "signature_header_present": true,
        "body": { "truncated": false, "bytes_total": 1284, "bytes_shown": 1284, "sha256": "6b8d4f0b…", "content": "{\"type\":\"invoice.payment_succeeded\"}" },
        "body_json": { "type": "invoice.payment_succeeded", "data": { "object": { "id": "in_123" } } }
      },
      "response": {
        "status": 500,
        "headers": { "content-type": ["text/plain"] },
        "body": { "truncated": false, "bytes_total": 36, "bytes_shown": 36, "sha256": "91c6f4b2…", "content": "database connection timeout" }
      }
    },
    "redactions": ["request.headers.stripe-signature"]
  }
}
events get truncates the body by default. Always check body.truncated; if it is true, re-fetch with --full for the complete payload. Use events diff to explain why one webhook behaved differently from another.
Credential headers and secret-like fields are redacted; PII is not. See Transcript safety before copying any payload into a transcript.

Inspect delivery attempts

Find failed deliveries with --failed, then pull the retry chain for one event and forwarder.
repost forwards search --bucket stripe-prod --forwarder prod-api --since 30m --failed --json

repost forwards chain evt_01JZ8V1 --bucket stripe-prod --forwarder prod-api --json
{
  "schema": "repost.forwards.chain/v1",
  "data": {
    "event_id": "evt_01JZ8V1",
    "forwarder_id": "fwd_01HV9S",
    "attempts": [
      { "forward_id": "fwd_attempt_01JZ8UZ", "attempt": 1, "type": "EXTERNAL", "status": "FAILED", "response_code": 503, "latency_ms": 220, "created_at": "2026-06-19T08:31:25Z", "will_retry": true, "next_retry_at": "2026-06-19T08:32:25Z" },
      { "forward_id": "fwd_attempt_01JZ8V2", "attempt": 3, "type": "EXTERNAL", "status": "FAILED", "response_code": 500, "latency_ms": 1840, "created_at": "2026-06-19T08:33:26Z", "will_retry": false }
    ]
  }
}
forwards search finds candidate attempts fast (use --failed rather than a status: query); forwards chain is the authoritative per-attempt timeline — order, response code, latency, retry intent, and terminal status — for one event and forwarder. Search to locate, chain to drill in.
The chain’s will_retry / next_retry_at are the forwarder’s automatic retries (its retry policy). Deliberately re-sending an event is a replay — a separate, manual action covered in Replay deliveries.

Triage the DLQ

When health reports DLQ depth, inspect the rows before replaying.
repost dlq list --bucket stripe-prod --forwarder prod-api --limit 20 --json
{
  "schema": "repost.dlq.list/v1",
  "data": {
    "items": [
      { "dlq_id": "01JZ8V6H5Q8YJ5HY8D7M2QW1X2", "event_id": "evt_01JZ8V1", "forward_id": "fwd_attempt_01JZ8V2", "forwarder_id": "fwd_01HV9S", "created_at": "2026-06-19T08:33:40Z" }
    ],
    "has_more": false
  }
}
Carry these dlq_ids straight into a replay to remediate exhausted deliveries.

Continue

Replay deliveries safely

Replay the failed event from its ID or the DLQ, behind a dry-run.

Wait for a webhook

Confirm a fix by waiting for the next matching event.

Transcript safety

What redaction covers, and what you must scrub yourself.