Webhook Guides

GitHub Webhook Retry Strategy

A GitHub webhook retry strategy should separate provider redelivery from FastHook destination recovery. GitHub can redeliver a provider delivery, but GitHub does not automatically redeliver failed deliveries. FastHook retry rules, event retry, request retry, and bulk retry operate after FastHook has accepted or stored the request.

The recovery rule is simple: prove where the failure happened, fix that layer, then retry the smallest safe slice. For GitHub automation, that means idempotency before replay, narrow filters before bulk operations, and attempt evidence before touching production deploys or pull request side effects.

Retry and redelivery model

GitHub redelivery sends the provider request again from GitHub. FastHook automatic retry schedules another destination attempt for an accepted event. FastHook event retry queues one existing event branch again. FastHook request retry replays the stored inbound request through current routing again.

GitHub webhook retry strategy flow showing GitHub redelivery, FastHook accepted request, destination attempts, automatic retry, event retry, request retry, and bulk retry.
Use GitHub redelivery when the provider delivery must be sent again. Use FastHook retries and replay when FastHook already has request or event evidence.
PathWhat happensUse whenTriggerEvidence
GitHub redeliveryGitHub sends the provider delivery againFastHook never received the request, or you want to test provider-side deliveryGitHub UI or REST APINew provider delivery attempt with GitHub headers
FastHook automatic retryFastHook schedules another destination attempt for the same eventReceiver returns retryable status or has a transport failureConnection retry ruleAttempt trigger AUTOMATIC
FastHook event retryFastHook queues one existing event branch againRouting was correct, but one destination failedPOST /v1/events/{id}/retryNew replay event id, attempt trigger MANUAL
FastHook request retryFastHook replays the stored inbound request through current routingFilters, routing, transformations, or source-level handling changedPOST /v1/requests/{id}/retryNew retry request through source pipeline
Bulk retryFastHook creates a batch operation from a narrow filterMany requests or events need the same recovery actionPOST /v1/events/bulk_operations or /v1/requests/bulk_operationsAttempt trigger BULK_RETRY for event bulk retry

FastHook automatic retry rule

A retry rule belongs to a connection. It only controls destination delivery after a request was accepted and routed into an event. count is the number of retry attempts after the first destination attempt.interval is milliseconds; exponential retry uses interval * 2^(attempt - 1).

Retry rule JSON
{
  "type": "retry",
  "strategy": "exponential",
  "interval": 30000,
  "count": 3,
  "response_status_codes": ["429", "500-599"]
}

Create a GitHub connection with retry

This connection retries throttling and server failures for GitHub automation. Permanent receiver contract errors should usually fail fast until the receiver or transformation is fixed.

Create retrying connection
curl -X POST "https://api.fasthook.io/v1/connections" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "github-main-automation",
    "source_id": "src_q6z62b6py5o79b",
    "destination_id": "des_TU9ioCk5EHUU",
    "rules": [
      {
        "type": "filter",
        "headers": {
          "x-github-event": { "$in": ["push", "pull_request"] }
        },
        "body": {
          "$or": [
            { "ref": "refs/heads/main" },
            { "pull_request": { "base": { "ref": "main" } } }
          ]
        }
      },
      {
        "type": "retry",
        "strategy": "exponential",
        "interval": 30000,
        "count": 3,
        "response_status_codes": ["429", "500-599"]
      }
    ]
  }'

Status code matching

FastHook accepts exact HTTP codes, ranges, comparisons, and multiple expressions in response_status_codes. Transport failures can be retried by the rule because there is no HTTP status to match.

PatternExampleUse
Exact status429Retry only one known recoverable status.
Range500-599Retry temporary server failures.
Comparison>=500Retry all 5xx responses.
Multiple values429, 503, 504Retry throttling and gateway failures.
Omitted listnullRetry any failed HTTP status when a retry rule exists.

Inspect before retry

Before retrying, inspect events and attempts. Events tell you whether work is queued, scheduled, held, failed, or successful. Attempts show the receiver response body, status, trigger, method, URL, and latency.

Inspect failed events
curl -G "https://api.fasthook.io/v1/events" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  --data-urlencode "source_id=src_q6z62b6py5o79b" \
  --data-urlencode "destination_id=des_TU9ioCk5EHUU" \
  --data-urlencode "status=FAILED" \
  --data-urlencode "from=now-24h" \
  --data-urlencode "to=now" \
  --data-urlencode "limit=50" \
  --data-urlencode "q=github"
Inspect scheduled events
curl -G "https://api.fasthook.io/v1/events" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  --data-urlencode "destination_id=des_TU9ioCk5EHUU" \
  --data-urlencode "status=SCHEDULED" \
  --data-urlencode "from=now-2h" \
  --data-urlencode "to=now" \
  --data-urlencode "limit=50"
List event attempts
curl -G "https://api.fasthook.io/v1/attempts" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  --data-urlencode "event_id=evt_01JYGITHUB" \
  --data-urlencode "order_by=created_at" \
  --data-urlencode "dir=asc" \
  --data-urlencode "limit=20"
Attempt evidence JSON
{
  "count": 2,
  "models": [
    {
      "id": "att_01JYFIRST",
      "event_id": "evt_01JYGITHUB",
      "destination_id": "des_TU9ioCk5EHUU",
      "attempt_number": 1,
      "trigger": "INITIAL",
      "status": "FAILED",
      "response_status": 503,
      "body": {
        "error": "deploy queue unavailable"
      },
      "http_method": "POST",
      "requested_url": "https://api.example.com/webhooks/github"
    },
    {
      "id": "att_01JYSECOND",
      "event_id": "evt_01JYGITHUB",
      "destination_id": "des_TU9ioCk5EHUU",
      "attempt_number": 2,
      "trigger": "AUTOMATIC",
      "status": "SUCCESSFUL",
      "response_status": 200,
      "body": {
        "ok": true
      },
      "http_method": "POST",
      "requested_url": "https://api.example.com/webhooks/github"
    }
  ]
}

Choose the smallest safe recovery path

Retry one event first when the original routing decision was correct. Retry one request when current routing should be evaluated again. Use bulk retry only after a single retry proves the receiver is healthy.

GitHub webhook retry decision runbook showing when to use GitHub redelivery, request retry, event retry, automatic retry, bulk retry, idempotency, and cancel controls.
Do not start with bulk retry. Find the failure layer, fix it, test one record, then widen recovery only if the destination stays healthy.

Retry one failed event branch

Retry one event
curl -X POST "https://api.fasthook.io/v1/events/evt_01JYGITHUB/retry" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"
Event retry response
{
  "ok": true,
  "queued": true,
  "transport": "queue",
  "retried_at": "2026-05-31T10:45:12.120Z",
  "event_id": "evt_01JYGITHUB",
  "replay_event_id": "evt_01JYREPLAY"
}

Replay one request through current routing

Retry one request
curl -X POST "https://api.fasthook.io/v1/requests/req_8bd1e3f2a4c94201/retry" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"
Request retry response
{
  "ok": true,
  "queued": true,
  "transport": "queue",
  "replayed_at": "2026-05-31T10:46:04.232Z",
  "request_id": "req_8bd1e3f2a4c94201",
  "source_id": "src_q6z62b6py5o79b",
  "replay_message_id": "rpl_7ca31a0f7aa14b14a6a1",
  "original_rejection_cause": null
}

Create a narrow event bulk retry

Event bulk retry
curl -X POST "https://api.fasthook.io/v1/events/bulk_operations" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "2026-05-31T10:00:00.000Z",
    "to": "2026-05-31T10:30:00.000Z",
    "status": "FAILED",
    "source_id": "src_q6z62b6py5o79b",
    "destination_id": "des_TU9ioCk5EHUU",
    "q": "github"
  }'

Create a narrow request bulk retry

Request bulk retry
curl -X POST "https://api.fasthook.io/v1/requests/bulk_operations" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "2026-05-31T10:00:00.000Z",
    "to": "2026-05-31T10:30:00.000Z",
    "status": "accepted",
    "source_id": "src_q6z62b6py5o79b",
    "q": "refs/heads/main"
  }'

Cancel a bulk operation if failures rise

Cancel bulk retry
curl -X POST "https://api.fasthook.io/v1/events/bulk_operations/bch_01JYRETRY/cancel" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_3b5335b627084a838b"

GitHub redelivery

Use GitHub redelivery when FastHook never received the provider request or when you specifically need GitHub to send the delivery again. For repository webhooks, GitHub exposes a REST endpoint that creates another delivery attempt for a delivery id.

GitHub redelivery API
curl -L -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer github_pat_xxx" \
  -H "X-GitHub-Api-Version: 2026-03-10" \
  "https://api.github.com/repos/OWNER/REPO/hooks/HOOK_ID/deliveries/DELIVERY_ID/attempts"

Event status and attempt triggers

Event status explains where work is waiting or why it stopped. Attempt trigger explains why the destination was called: initial delivery, automatic retry, manual event retry, bulk retry, or unpause drain.

StatusMeaning
QUEUEDDelivery work is waiting to be sent.
SCHEDULEDDelivery is waiting on retry backoff, rate limit, or another scheduled send path.
HOLDThe connection is paused and routed work is being held.
FAILEDRetry rules were exhausted, no rule matched, or the destination failed without a retry path.
SUCCESSFULA destination attempt completed successfully.
CANCELLEDThe event was cancelled or ignored by an operational control.

GitHub idempotency keys

Any retry path can deliver duplicate business work. Store X-GitHub-Delivery for tracing, but guard side effects with event-specific keys that represent the business action.

GitHub idempotency key
export function githubIdempotencyKey(headers, payload) {
  const eventType = headers["x-github-event"];
  const deliveryId = headers["x-github-delivery"];
  const repoId = payload.repository?.id ?? payload.repository?.full_name;

  if (eventType === "push") {
    return ["github", "push", repoId, payload.ref, payload.after].join(":");
  }

  if (eventType === "pull_request") {
    return [
      "github",
      "pull_request",
      payload.pull_request.id,
      payload.action,
      payload.pull_request.head?.sha
    ].join(":");
  }

  if (eventType === "workflow_run") {
    return ["github", "workflow_run", payload.workflow_run.id, payload.workflow_run.status].join(":");
  }

  return ["github", eventType, deliveryId].join(":");
}

Production recovery checklist

  • Verify X-Hub-Signature-256 at source before accepting retryable GitHub traffic.
  • Return success only after your receiver has durably recorded the work or a dedupe no-op.
  • Use retry rules for temporary 429, 503, 504, 5xx, and transport failures.
  • Avoid retrying permanent 400, 401, 403, 404, and schema errors until the contract is fixed.
  • Use event retry for one failed destination branch after the receiver is fixed.
  • Use request retry when routing, filters, transformations, or source handling changed.
  • Use GitHub redelivery only when the provider request itself must be sent again.
  • Start bulk retry with a narrow time window, source, destination, status, and search term.
  • Watch scheduled, queued, failed, successful, and held event counts while recovery drains.

GitHub webhook retry FAQ

Does GitHub automatically retry failed webhook deliveries?

No. GitHub records failed deliveries and lets you redeliver them manually or through the REST API, but GitHub does not automatically redeliver failed webhook deliveries.

How long can GitHub webhook deliveries be redelivered?

GitHub documents redelivery for webhook deliveries that occurred in the past 3 days on GitHub.com.

What does FastHook retry count mean?

FastHook retry count is the number of retry attempts after the first delivery attempt. For example, count 3 allows retry attempts 1, 2, and 3 after the initial attempt.

Should I use FastHook event retry or request retry?

Use event retry when one already-routed destination branch failed. Use request retry when the original request should run through current routing, filters, transformations, deduplication, and pause state again.

Is X-GitHub-Delivery enough for idempotency?

Use X-GitHub-Delivery for tracing, but add a business key such as repository id, commit SHA, pull request id, action, or workflow run id before triggering side effects.

GitHub documentation references

GitHub states that it does not automatically redeliver failed deliveries in handling failed webhook deliveries. GitHub documents manual and REST redelivery, including the 3-day redelivery window, in redelivering webhooks. The repository webhook REST reference includes the redelivery endpoint at REST API endpoints for repository webhooks.

Related guides