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

# Webhooks setup and configuration

> What Sakawave will accept as an endpoint, how the secret works, and every control that lives in the dashboard.

## Everything here is a dashboard action

<Warning>
  There is no webhook management endpoint on the Business API. Configuring the URL, enabling or disabling the endpoint, rotating the secret, sending a test event, reading delivery history, and resending a delivery are all dashboard actions. An API-only integration cannot do any of them.
</Warning>

| Action | Where |
| - | - |
| Set or change the URL | Dashboard |
| Enable or disable | Dashboard |
| Rotate the signing secret | Dashboard |
| Send a test event | Dashboard |
| Read delivery history | Dashboard |
| Resend a delivery | Dashboard |

## What Sakawave will accept as a URL

The URL is validated when you set it **and again on every delivery**. It must:

* use **HTTPS** — plain HTTP is rejected
* carry no username, password, or fragment
* be at most 2048 characters
* resolve **only** to public internet addresses

That last rule is what stops a webhook endpoint being used to reach inside Sakawave's own network. Every address a hostname resolves to must be public; if any is private, loopback, link-local, or otherwise reserved, the URL is refused with `webhook_url_invalid`. A hostname that does not resolve at all gives `webhook_hostname_unavailable`.

<Note>
  You cannot point an endpoint at `localhost` or a private address, so a local receiver needs a public tunnel during development. The check runs again at delivery, so a hostname that later resolves to a private address stops being delivered to.
</Note>

Sakawave does not follow redirects. Point the endpoint at the final URL.

## The signing secret

A `whsec_…` secret is disclosed exactly twice in its life: when the endpoint is created, and when it is explicitly rotated. Sakawave stores a verifier rather than the secret, so it cannot be shown again or recovered.

| Action | Rotates the secret |
| - | - |
| Create the endpoint | Yes — issues the first one |
| **Change the URL** | **No** |
| Explicit rotation | Yes |
| Disable and re-enable | No |

Changing the URL keeps the secret, which is what you want when moving between hosts.

**Rotating** replaces it immediately: deliveries signed with the old secret stop being produced the moment the new one is issued. Since there is no overlap window, deploy the new secret to your receiver first, then rotate, and accept a brief period where the receiver holds both.

```ts theme={null}
// Accept either secret across a rotation, then drop the old one.
const secrets = [process.env.SAKAWAVE_WEBHOOK_SECRET, process.env.SAKAWAVE_WEBHOOK_SECRET_PREVIOUS]
  .filter(Boolean);

const valid = secrets.some((secret) => matches(secret, id, timestamp, rawBody, signature));
```

## One endpoint, one environment

A business has one active endpoint per environment. Sandbox and live are separate deployments with separate endpoints and separate secrets.

Enabling an endpoint does **not** backfill. Events that occurred while no endpoint was active were never staged for delivery, and no resend will produce them — reconcile through the REST resources instead.

## Timeouts, and what counts as delivered

| Property | Value |
| - | - |
| Request timeout | **10 seconds** |
| Attempts | **1** |
| Automatic retry | **None** |
| Acknowledgement | Any `2xx` |
| Redirects | Not followed |
| Response body recorded | Up to 4 KB, for delivery history |

Anything that is not a `2xx` within ten seconds marks the delivery **exhausted**. That is terminal without a manual resend.

Because the response body is recorded and visible in the dashboard, do not echo anything sensitive back — return an empty `204`.

## Delivery history

Deliveries are retained for **30 days** and viewable in the dashboard, with the request payload, the response status, a bounded response body, the attempt count, and any error code.

A resend reuses the original event id and payload; the timestamp, signature, and attempt number change. That is why deduplication must be on the id and not on the signature.

## Failure handling on the endpoint

Repeated failures are recorded against the endpoint. Disabling, replacing, or deleting an endpoint cancels its pending deliveries; re-enabling affects future events only.

## Configuration checklist

* [ ] HTTPS URL that resolves only to public addresses, with no redirect in front of it
* [ ] Secret stored in a secrets manager the moment it is shown
* [ ] Receiver able to accept two secrets during a rotation
* [ ] Raw-body route registered before any JSON body parser
* [ ] Deduplication on `Sakawave-Webhook-Id`, with a unique index
* [ ] Acknowledgement inside ten seconds, with the work queued
* [ ] Empty `204` returned — the response body is recorded
* [ ] A reconciliation job for anything that moves money
* [ ] Sandbox and live configured independently, with different secrets


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.