Skip to main content
A webhook route has two halves: a bucket receives inbound requests at a public URL, and a forwarder delivers them to your target. Provisioning means creating both, wiring the forwarder to the bucket, and confirming the topology. Both are write mutations — see Safe mutations.
For the common case, repost init --name "Stripe production" --target https://api.example.com/webhooks --json creates both halves in one idempotent call — re-running with the same name and target returns the existing route instead of duplicating it. Use the step-by-step flow below when you need to configure the bucket or forwarder beyond init’s defaults. Run repost docs schema "init" for its exact output shape.

Create a bucket

The bucket’s internal_domain is its receiving URL — point the provider at https://<internal_domain>. The name is a positional argument, not a flag.
repost bucket create "Stripe production" \
  --response-mode QUEUE \
  --ack-status-code 200 \
  --json
{
  "schema": "repost.bucket.create/v1",
  "data": {
    "bucket": {
      "id": "bkt_01HV9S",
      "name": "Stripe production",
      "slug": "stripe-prod",
      "internal_domain": "stripe-prod.repost.sh",
      "is_active": true,
      "response_mode": "QUEUE",
      "ack_status_code": 200
    },
    "created": true
  }
}
Capture bucket.id (needed to attach a forwarder) and bucket.slug (used by every diagnostic command).
--response-mode decides how the bucket answers the provider:
  • QUEUE (default) — acknowledge immediately with --ack-status-code / --ack-body, then forward asynchronously. Best for reliability.
  • PROXY — forward synchronously and return the target’s response, bounded by --proxy-timeout-ms. Use only when the provider needs the real downstream response.

Create a forwarder

Attach a forwarder to the bucket by slug. Bucket ID is accepted for compatibility, but agents should prefer the slug from repost bucket list. The name is positional; for an EXTERNAL forwarder (the default type), --target is required.
repost forwarder create prod-api \
  --bucket stripe-prod \
  --target https://api.example.com/webhooks \
  --rate-limit-per-second 25 \
  --json
{
  "schema": "repost.forwarder.create/v1",
  "data": {
    "forwarder": {
      "id": "fwd_01HV9S",
      "bucket_id": "bkt_01HV9S",
      "name": "prod-api",
      "type": "EXTERNAL",
      "is_active": true,
      "is_paused": false,
      "path_mode": "LOCKED",
      "target_url": "https://api.example.com/webhooks",
      "http_timeout": 30000,
      "max_retries": 3,
      "retry_delay_ms": 15000,
      "retry_backoff": 2,
      "rate_limit_per_second": 25
    },
    "created": true
  }
}
Retry and rate behavior is set at creation. The defaults are sensible for most targets:
FlagDefaultControls
--max-retries3Delivery attempts before a row reaches the DLQ.
--retry-delay-ms15000Initial backoff before the first retry.
--retry-backoff2.0Multiplier applied to each successive delay.
--retry-max-delay-ms900000Ceiling on backoff (15 min).
--rate-limit-per-second0 (off)Max deliveries/sec from this forwarder.
--http-timeout-ms30000Per-attempt request timeout.
--rate-limit-per-second is a persistent cap on all deliveries from this forwarder — it’s what tames the burst when you resume after a deploy. It is not the same as replay --rate, which throttles a single replay job.

Provision idempotently

init is idempotent; the individual create commands are not.
bucket create and forwarder create generate a fresh idempotency key each run, so a blind retry creates a duplicate. When you use them instead of init, check existence first and only create what is missing.
1

List before creating

repost bucket list --json and repost forwarder list --bucket <bucket-slug> --json. Match on name / slug / target_url.
2

Create only the gap

If the bucket exists, reuse its id. If a forwarder with that target exists, reuse it. Create only what the lists do not already contain.
3

Record the IDs

Persist the returned bucket.id and forwarder.id so later runs skip creation entirely.

Inspect the topology

bucket get returns the bucket plus its active forwarders — the authoritative view of one route.
repost bucket get stripe-prod --json
{
  "schema": "repost.bucket.get/v1",
  "data": {
    "bucket": {
      "id": "bkt_01HV9S",
      "slug": "stripe-prod",
      "internal_domain": "stripe-prod.repost.sh",
      "is_active": true
    },
    "forwarders": [
      {
        "id": "fwd_01HV9S",
        "name": "prod-api",
        "type": "EXTERNAL",
        "target_url": "https://api.example.com/webhooks",
        "is_active": true,
        "is_paused": false
      }
    ]
  }
}

Verify end to end

Provisioning is not done until a real request flows through. Send a test webhook to https://<internal_domain>, then confirm both halves:
repost expect --bucket stripe-prod --filter 'method:POST' --timeout 30s
# → { "event_id": "evt_01JZ8V1", "bucket_id": "bkt_01HV9S", ... }
A clean expect (exit 0) proves the bucket received the request. Running forwards chain on that event_id then shows the delivery attempt — a SUCCEEDED status proves the forwarder delivered. Together they confirm the route is live end to end. (Filter failures with forwards search --failed, not a status: query — see Search & filters.)

Continue

Guard deployments

Pause and resume the forwarders you just created.

Diagnose webhooks

Inspect what the new route receives and delivers.

Forwarders

Path modes, transforms, and the complete forwarder model.