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

# Testing Webhooks

Test webhooks in two stages: use the dashboard's **Test** action for a quick
receiver check, then send one filtered canary request to exercise the complete
production path.

## Quick receiver test

<Steps>
  <Step title="Run a local receiver">
    This Node.js example verifies the signature from the raw body before parsing
    the payload:

    ```javascript theme={null}
    import crypto from "node:crypto";
    import express from "express";

    const app = express();

    app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
      const rawBody = req.body;
      const signature = req.header("Planck-Signature") ?? "";
      const expected = crypto
        .createHmac("sha256", process.env.PLANCK_WEBHOOK_SECRET)
        .update(rawBody)
        .digest("hex");

      if (
        !/^[0-9a-f]{64}$/i.test(signature) ||
        !crypto.timingSafeEqual(
          Buffer.from(expected, "hex"),
          Buffer.from(signature, "hex")
        )
      ) {
        return res.status(401).send("invalid signature");
      }

      const payload = JSON.parse(rawBody.toString("utf8"));
      console.log({
        requestId: payload.request_id,
        deliveryId: req.header("Planck-Delivery-Id"),
        attempt: req.header("Planck-Delivery-Attempt"),
      });
      return res.status(202).send("accepted");
    });

    app.listen(8000);
    ```
  </Step>

  <Step title="Expose it over HTTPS">
    Use an HTTPS tunnel such as ngrok:

    ```bash theme={null}
    ngrok http 8000
    ```

    Copy the forwarding URL and append `/webhook`, for example
    `https://abc123.ngrok-free.app/webhook`.
  </Step>

  <Step title="Use the dashboard Test action">
    Add the HTTPS destination under **Settings → Webhooks**, copy its HMAC key into
    `PLANCK_WEBHOOK_SECRET`, and click **Test**. A successful test proves that the
    destination is reachable, returns 2xx, and accepts the signed synthetic
    payload.

    <Note>
      The Test action is synchronous and does not use the durable outbox, delivery
      retries, or production request consumer.
    </Note>
  </Step>
</Steps>

## Production end-to-end canary

Use this procedure after deploying webhook or logging changes:

1. Create a temporary webhook or update a test destination with the property
   filter `canary = manual-log`. Keep its sample rate at 100%.
2. Run the repository canary with a production-scoped test key:

```bash theme={null}
PLANCK_API_KEY="sk-planck-..." bash planck-gateway/scripts/test-production-manual-log.sh
```

3. Confirm your receiver gets the same `request_id`, a
   `Planck-Delivery-Id`, and `Planck-Delivery-Attempt: 1`.
4. Confirm the script finds the request through the request API. Remove or
   disable the temporary webhook when finished.

The canary logs a synthetic custom-tool operation and adds
`Planck-Property-Canary: manual-log`; it does not call an LLM or third-party
tool. Normal production requests do not match the filter.

<Warning>
  Use a dedicated canary destination and a narrowly scoped API key. Do not test
  retries by intentionally failing a shared production receiver.
</Warning>

## Retry and idempotency test

In a non-production environment, make the receiver return HTTP 500 once and
then return 202. Verify that:

* the second request has the same `Planck-Delivery-Id`;
* `Planck-Delivery-Attempt` increases;
* your queue or database deduplicates the event by delivery ID.

Return 400 to test a permanent failure. Planck retries only timeouts, HTTP
408, 425, 429, and 5xx responses.

## Common failures

| Symptom                          | Check                                                                                                                         |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `401 invalid signature`          | Verify the raw body bytes, not parsed/re-serialized JSON                                                                      |
| No canary delivery               | Confirm filter key `canary`, value `manual-log`, and sample rate 100%                                                         |
| Repeated events                  | Deduplicate using `Planck-Delivery-Id`                                                                                        |
| Missing `request_response_url`   | It is present only for S3-backed bodies when enhanced data is enabled                                                         |
| Encrypted body URL is unreadable | Application-encrypted S3 objects require the authenticated request API; encrypted webhook body retrieval is not yet available |
| Immediate permanent failure      | Confirm the final URL is public HTTPS and does not redirect                                                                   |

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/features/webhooks">
    Review payloads, delivery guarantees, storage, and signature verification
  </Card>

  <Card title="Trace tools with cURL" icon="terminal" href="/integrations/tools/curl">
    Inspect the manual logging endpoint and production canary payload
  </Card>
</CardGroup>

***

<Accordion title="Need more help?">
  Additional questions or feedback? Reach out to
  [help@inquantum.ai](mailto:help@inquantum.ai) or [schedule a
  call](https://inquantum.ai/contact) with us.
</Accordion>
