> ## Documentation Index
> Fetch the complete documentation index at: https://supahooks.ar27111994.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshoot common Webhook Debugger issues

> Diagnose and fix common problems with authentication, signature verification, payload handling, replay, streaming, and general endpoint behavior.

This page covers the most common issues you may encounter when running Webhook Debugger & Logger and explains how to resolve each one. Issues are grouped by area so you can jump to the section that matches your symptom.

## Authentication issues

<AccordionGroup>
  <Accordion title="Every request returns 401">
    When `authKey` is configured, all management endpoints and the dashboard require the key. Pass it using one of these methods:

    ```bash theme={null}
    # Bearer token in Authorization header
    curl -H "Authorization: Bearer <your-key>" "https://<run-id>.runs.apify.net/logs"

    # Query parameter
    curl "https://<run-id>.runs.apify.net/logs?key=<your-key>"
    ```

    The key is **case-sensitive**. Verify that the value you are sending exactly matches the `authKey` you configured in the actor input.
  </Accordion>

  <Accordion title="Webhook ingest returns 401">
    When `authKey` is set, webhook ingest (`/webhook/:id`) also requires authentication — it is not public. Add the key to your provider's webhook configuration as a header or query parameter.

    For providers that support custom headers, set:

    ```
    Authorization: Bearer <your-key>
    ```

    For providers that only support query parameters, append `?key=<your-key>` to the webhook URL.

    <Note>
      `/health` and `/ready` are never protected by `authKey`. These endpoints are intentionally public so orchestrators and load balancers can probe them regardless of your auth configuration.
    </Note>
  </Accordion>

  <Accordion title="403: IP blocked">
    Your source IP is not in the `allowedIps` list. Check your egress IP address and add it to the allowlist in your actor input.

    ```json theme={null}
    {
      "allowedIps": ["203.0.113.10/32", "198.51.100.0/24"]
    }
    ```

    If you are behind NAT or a proxy, the IP seen by the actor may differ from your local IP. Use a service like `https://api.ipify.org` to confirm your public egress address.
  </Accordion>
</AccordionGroup>

## Signature verification failures

<AccordionGroup>
  <Accordion title="Stripe: signatureValid is false">
    Check two things:

    1. **Secret mismatch** — `signatureVerificationSecret` must match the signing secret shown in your Stripe dashboard. Stripe signing secrets start with `whsec_`. Copy and paste the value directly to avoid whitespace errors.

    2. **Clock skew** — Stripe includes a timestamp in its `Stripe-Signature` header. If the timestamp is older than the configured `tolerance` (default: 300 seconds), verification fails. Check that your system clock is accurate and increase `tolerance` if needed.

    ```json theme={null}
    {
      "signatureVerification": {
        "provider": "stripe",
        "secret": "whsec_...",
        "tolerance": 300
      }
    }
    ```
  </Accordion>

  <Accordion title="GitHub: signature failing">
    Ensure that `signatureVerification.secret` exactly matches the secret you entered in the GitHub webhook settings. GitHub uses `sha256=<hex>` in the `X-Hub-Signature-256` header, so the algorithm must be `sha256`.

    Double-check for trailing whitespace or newline characters — these are a common source of mismatches when copying secrets from a terminal or text editor.

    ```json theme={null}
    {
      "signatureVerification": {
        "provider": "github",
        "secret": "your-github-webhook-secret"
      }
    }
    ```
  </Accordion>

  <Accordion title="Slack: signature failing">
    Slack includes a timestamp in the `X-Slack-Request-Timestamp` header and rejects requests that are too old, even if the HMAC matches. If your system clock is skewed, Slack's own servers will send timestamps that fall outside your `tolerance` window.

    Steps to diagnose:

    1. Verify your system clock is synchronized (NTP).
    2. Check the `tolerance` setting — the default is 300 seconds. If your environment has a large clock offset, increase this value.
    3. Confirm `signatureVerificationSecret` matches the signing secret from your Slack App configuration under **Basic Information > App Credentials > Signing Secret**.
  </Accordion>
</AccordionGroup>

## Payload issues

<AccordionGroup>
  <Accordion title="413: request body too large">
    The request body exceeds `maxPayloadSize` (default: 10 MB, maximum: 100 MB). You have two options:

    * **Increase the limit** — raise `maxPayloadSize` in your actor input up to `104857600` (100 MB).
    * **Reduce the payload** — strip unnecessary fields or compress the body before sending.

    ```json theme={null}
    {
      "maxPayloadSize": 52428800
    }
    ```

    <Warning>
      Values above 100 MB are clamped to the maximum. If your use case requires larger payloads, consider splitting the payload across multiple requests.
    </Warning>
  </Accordion>

  <Accordion title="400: JSON Schema validation rejected the payload">
    The incoming payload does not match the `jsonSchema` you configured. To diagnose:

    1. Retrieve the raw payload from `/logs/:logId/payload` to see exactly what was sent.
    2. Validate the payload against your schema using a tool like the [JSON Schema Validator](https://www.jsonschemavalidator.net/).
    3. Adjust either your schema or the payload structure to align them.

    If you want to accept the payload while you investigate, temporarily remove the `jsonSchema` input via hot-reload.
  </Accordion>

  <Accordion title="Body shows as a string instead of a parsed object">
    Two common causes:

    1. **`enableJSONParsing` is `false`** — set it to `true` in your actor input. When enabled, the actor automatically parses `application/json` bodies into structured objects.

    2. **Missing or incorrect `Content-Type` header** — the actor uses the `Content-Type` header to decide whether to parse the body. Ensure your sender is setting `Content-Type: application/json`.

    ```bash theme={null}
    curl -X POST "https://<run-id>.runs.apify.net/webhook/wh_abc123" \
      -H "Content-Type: application/json" \
      -d '{"event":"test"}'
    ```
  </Accordion>
</AccordionGroup>

## Replay and forwarding issues

<AccordionGroup>
  <Accordion title="Replay returns 400: Missing url parameter">
    The `url` query parameter is required for every replay request. Add the destination URL to your replay call:

    ```bash theme={null}
    curl -X POST \
      "https://<run-id>.runs.apify.net/replay/<webhookId>/<itemId>?url=https://target.example/webhook"
    ```

    Make sure the URL is properly percent-encoded if it contains special characters.
  </Accordion>

  <Accordion title="Replay returns an SSRF error">
    The destination URL resolves to a private, loopback, link-local, or cloud metadata IP address. The actor blocks these targets to prevent server-side request forgery.

    Blocked ranges include `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, and the cloud metadata address `169.254.169.254`.

    Use a publicly routable hostname or IP as your replay destination. If you are testing locally, use a tunnel service to expose your local server with a public URL.
  </Accordion>

  <Accordion title="Forwarding is not reaching the destination">
    Check these in order:

    1. **`forwardUrl` is correct and publicly reachable** — confirm the URL responds to POST requests from the public internet.
    2. **Circuit breaker** — if the destination was unreachable or returning errors, the circuit breaker may have tripped. Wait approximately 30 seconds for it to reset automatically.
    3. **`maxForwardRetries`** — the default is 3. If all retries are exhausted without a successful response, the forward attempt is marked as failed.
    4. **SSRF protection** — forwarding to private or reserved IP ranges is blocked for the same reason as replay (see above).

    Enable alerts on `error` or `5xx` to be notified immediately when forwarding fails.
  </Accordion>
</AccordionGroup>

## Streaming issues

<AccordionGroup>
  <Accordion title="SSE stream returns 503">
    The actor enforces a maximum of 100 concurrent SSE client connections. When this limit is reached, new connections receive `503 Service Unavailable`.

    To resolve this, close any idle or unused SSE connections. Once the active connection count drops below the limit, new connections will be accepted.
  </Accordion>

  <Accordion title="No events appearing in the SSE stream">
    Two common causes:

    1. **Auth header missing** — if `authKey` is configured, the stream endpoint requires authentication. Pass the key as a header:

       ```bash theme={null}
       curl -N -H "Authorization: Bearer <your-key>" \
         "https://<run-id>.runs.apify.net/log-stream"
       ```

    2. **Webhook ID has expired** — the stream delivers events for all active webhooks in the run. If all webhooks have expired, no events will arrive. Call `GET /info` to confirm you have at least one active webhook and check its `expiresAt` time.
  </Accordion>
</AccordionGroup>

## General issues

<AccordionGroup>
  <Accordion title="Webhook URL returns 404">
    The webhook ID does not exist or has expired. Common causes:

    * The `retentionHours` window elapsed and the webhook stopped accepting traffic.
    * The webhook ID was typed incorrectly.
    * The actor was restarted and a new set of webhook IDs was generated.

    Call `GET /info` to see all currently active webhooks and their expiry times. Use one of the returned IDs for your requests.
  </Accordion>

  <Accordion title="Logs are not showing recent events">
    The `/logs` endpoint queries DuckDB, which syncs from the Apify Dataset asynchronously. There is typically a short lag between when an event is captured and when it appears in query results.

    The Apify Dataset is the source of truth. If an event was captured, it exists in the Dataset even before DuckDB reflects it.

    To check the current sync status, call `GET /system/metrics`:

    ```bash theme={null}
    curl "https://<run-id>.runs.apify.net/system/metrics"
    ```

    The response includes `sync.lastSyncTime`, `sync.itemsSynced`, and `sync.errorCount`. If `errorCount` is rising, there may be a sync issue worth investigating.
  </Accordion>

  <Accordion title="/ready returns 503">
    The actor is not yet ready to serve traffic. This usually means DuckDB or the webhook state has not finished initializing. Wait a few seconds and retry — the actor is likely still starting up.

    If the `/ready` endpoint continues returning `503` after 30 seconds, check the actor logs for initialization errors. Restarting the actor typically resolves transient startup failures.

    <Note>
      `/health` and `/ready` are never protected by `authKey`, so you can always probe them directly without authentication.
    </Note>
  </Accordion>
</AccordionGroup>
