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

# Webhooks

> Receive real-time HTTP notifications when events happen in your Overflow account.

Webhooks let your application **receive real-time HTTP notifications from Overflow** the moment something important happens in your nonprofit's account: a contribution is approved, a donor is created, a recurring gift fails, a deposit is paid out, and more.

Instead of polling the [Overflow API](/overflow-for-nonprofits/developers/api) on a schedule, register an HTTPS endpoint, choose the events you care about, and Overflow will POST a signed JSON payload to your URL as each event fires.

<Note>
  This is the same push-based integration pattern used by Stripe, GitHub, Shopify, and most major SaaS platforms. If you've integrated webhooks before, the model here will feel familiar.
</Note>

## When to use webhooks

Use webhooks when your integration needs to **react** to activity in Overflow, rather than reconcile state on a timer. Common use cases:

* **Real-time CRM sync.** A donor finishes a stock gift → their CRM record updates in seconds.
* **Automated receipting and acknowledgments.** Contribution approved → trigger your thank-you email pipeline.
* **Finance and reconciliation automation.** Deposit paid out → ping your accounting workflow.
* **Recurring gift recovery.** Recurring gift failed → enroll the donor in a dunning sequence.
* **Internal dashboards and alerts.** Pipe events into Slack, Datadog, or internal tooling.
* **No-code automation.** Wire your endpoint to Zapier, Make, n8n, or any HTTP listener.

## Event catalog

You can subscribe to any combination of the events below, or to **all events** via a wildcard (`*`). Wildcard subscriptions automatically receive new event types the moment Overflow ships them.

### Contributions

| Event                       | When it fires                                                          |
| --------------------------- | ---------------------------------------------------------------------- |
| `contribution.approved`     | A contribution has been approved.                                      |
| `contribution.declined`     | A contribution was declined.                                           |
| `contribution.processing`   | A contribution is in flight.                                           |
| `contribution.paid_out`     | Funds for the contribution have been paid out.                         |
| `contribution.voided`       | A contribution was voided.                                             |
| `contribution.chargeback_*` | Lifecycle events for chargebacks (3 variants).                         |
| `contribution.refund_*`     | Lifecycle events for refunds, including full and partial (6 variants). |

### Donors

| Event            | When it fires                   |
| ---------------- | ------------------------------- |
| `donor.created`  | A new donor record was created. |
| `donor.updated`  | A donor record was updated.     |
| `donor.archived` | A donor record was archived.    |
| `donor.merged`   | Two donor records were merged.  |

### Recurring gifts

| Event                         | When it fires                                      |
| ----------------------------- | -------------------------------------------------- |
| `recurring_gift.active`       | A recurring gift moved to the active state.        |
| `recurring_gift.inactive`     | A recurring gift was deactivated.                  |
| `recurring_gift.paused`       | A recurring gift was paused.                       |
| `recurring_gift.updated`      | A recurring gift's configuration changed.          |
| `recurring_gift.failed`       | A recurring gift failed terminally.                |
| `recurring_gift.cycle_failed` | A single billing cycle of a recurring gift failed. |

### Deposits

| Event              | When it fires                                     |
| ------------------ | ------------------------------------------------- |
| `deposit.paid_out` | A deposit has been paid out to your bank account. |

### Wildcard

Selecting **every event in every group** collapses your subscription to `*`. A wildcard subscription receives every event Overflow emits today **and** every event Overflow adds in the future, automatically. If you need stability across releases, subscribe to an explicit list instead.

## Create a subscription

Webhook subscriptions are managed from your nonprofit dashboard under **Developers → Webhooks**. From the list page, click **+ New Subscription** to open the 3-step wizard.

<Steps>
  <Step title="Choose events">
    Use the grouped event tree (Contributions, Donors, Recurring Gifts, Deposits) to select individual events, an entire group, or everything. Picking everything is submitted as the wildcard `*`.
  </Step>

  <Step title="Endpoint details">
    Provide:

    * **Name** (required, up to 80 characters): how the subscription appears in the list.
    * **Endpoint URL** (required, **HTTPS only**): where Overflow will POST events. `http://` is rejected at both the form and API level.
    * **Description** (optional, up to 500 characters): free-form notes for your team.
  </Step>

  <Step title="Save the signing secret">
    Overflow displays the unmasked **signing secret** one time, with a copy button. **Copy it now** and store it in your secret manager; after this step, the dashboard only shows a masked version.

    If you lose it, you must [roll the secret](#rotate-the-signing-secret); there is no way to retrieve the original.
  </Step>
</Steps>

## Verify webhook signatures

Every webhook Overflow sends carries an HMAC-SHA256 signature in the `x-overflow-webhook-signature-hmacsha256` header. Your receiver should recompute the signature using its stored secret and reject any request whose signature does not match.

Signing secrets are shaped `whsk_v1_hs256_<random>`. The prefix encodes the version and algorithm so Overflow can introduce stronger algorithms in the future without breaking existing integrations.

### Example verification (Node.js)

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

export function verifyOverflowSignature(req, secret) {
  const signature = req.headers["x-overflow-webhook-signature-hmacsha256"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(req.rawBody) // Buffer or string of the exact bytes received
    .digest("hex");

  // Use timing-safe comparison
  const a = Buffer.from(signature ?? "", "hex");
  const b = Buffer.from(expected, "hex");
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}
```

<Warning>
  Verify against the **raw request body**, not a re-serialized JSON object. Any whitespace difference will invalidate the signature.
</Warning>

## Rotate the signing secret

From the subscription's **Overview** tab, click **Roll Secret** on the Signing Secret card. You'll be prompted to pick a **grace window**:

* Immediately
* 1 hour
* 6 hours
* 12 hours
* 24 hours
* 72 hours
* 1 week

During the grace window, **Overflow signs every outbound webhook with both the previous secret and the new one**. Your receiver can deploy the new secret on its own schedule without dropping events.

Up to **10 previous secrets** are retained per subscription to support dual-signing. The oldest is removed first when the limit is reached. Each rolled secret is listed with its last 4 characters, when it was rolled, and when it expires.

## Retries and delivery guarantees

Overflow attempts each delivery independently per subscription. Failed deliveries with **transient** errors are retried up to **16 times over a 3-day+ exponential backoff**. Failures that indicate a persistent misconfiguration stop retrying immediately so you find out fast.

| HTTP response                                                 | Retried?                              |
| ------------------------------------------------------------- | ------------------------------------- |
| `2xx` Success                                                 | No, marked **Delivered**              |
| `408` Request Timeout                                         | Yes                                   |
| `429` Too Many Requests                                       | Yes                                   |
| `5xx` Server Error                                            | Yes                                   |
| Connection error / DNS failure / timeout                      | Yes                                   |
| Any other `4xx` (`400`, `401`, `403`, `404`, `410`, `422`, …) | **No**, marked **Failed** immediately |

<Tip>
  Return a `2xx` response as quickly as possible. If you need to do significant work, enqueue the event in your own job system and respond `200` immediately; long handlers risk hitting our request timeout and being retried.
</Tip>

## Delivery health notifications

When automated deliveries to a subscription start failing, Overflow emails the **subscription creator** (the admin who created the subscription) so someone on your team knows to look at the endpoint before events are missed. There are two emails, sent from `Overflow <noreply@overflow.co>`:

* **Webhook deliveries failing** — the endpoint is unhealthy, retries may still be in progress, and the subscription is **still enabled**.
* **Webhook subscription disabled** — Overflow **auto-disabled** the subscription after a terminal delivery failure. Deliveries have stopped until someone re-enables it.

Both emails include the nonprofit name, subscription name, destination URL, failure context (event type, error message, HTTP status, attempt `n` of `max`), and a link back to the subscription in the dashboard — to **Event Deliveries** for a failing email, and to the subscription detail for an inactive email.

### Failing email cadence

A run of failures opens a **degradation episode** for the subscription. While the subscription is still enabled, reminder emails are gated by how long the episode has been open, not by every individual HTTP failure:

| Stage     | Sends when the episode has been open for…                                    |
| --------- | ---------------------------------------------------------------------------- |
| 1         | ≥ **30 minutes** — heads-up that the endpoint has been failing.              |
| 2         | ≥ **12 hours** — still failing; auto-disable is approaching.                 |
| 3 (final) | ≥ **24 hours** — final warning before the subscription can be auto-disabled. |

Each stage sends at most **once per episode**, so a failure storm does not translate into an email storm.

### When Overflow auto-disables a subscription

Auto-disable is driven by a **terminal delivery failure**, not by a hard clock at 24 hours. A terminal failure is either:

* A non-retriable HTTP response (most `4xx`s), or
* A retriable failure that has exhausted the retry budget (up to 16 attempts over the multi-day backoff).

On a terminal failure Overflow closes the episode, **disables** the subscription if it was still enabled, and emails the creator the **inactive** notification.

<Warning>
  Auto-disable is terminal-failure driven, not time-bound. A persistent `404` or `401` can disable the subscription quickly (no multi-day wait). A flapping `5xx` may send failing reminders for hours while retries continue, and only disable once the retry budget is exhausted. Treat the 24-hour stage as a **warning horizon**, not a guaranteed disable time.
</Warning>

<Note>
  Health emails are sent to the **subscription creator only** — other nonprofit admins are not CC'd. If the creator has left the org or has a stale email on file, notifications may not reach the team currently operating the endpoint. Only automated delivery failures feed this path: **manual Resend** and **Send Test Event** do not trigger health emails.
</Note>

### What to do when you receive one

1. Open the link in the email and review recent attempts on the **Event Deliveries** tab (status, response body, timing).
2. Fix the endpoint (uptime, auth, URL, TLS) — or temporarily **Disable** the subscription if you are not ready to receive traffic.
3. If you received the disable email, re-enable the subscription from the dashboard once the endpoint is healthy.
4. Use **Send Test Event** to confirm the receiver before relying on live traffic again.

## View deliveries and resend

Open any subscription and switch to the **Event Deliveries** tab to inspect every attempt Overflow has made to your endpoint, grouped by day.

Each row shows the event name, HTTP status, attempt timestamp, and a retry badge if it is not the first attempt. Filter by event name or by status (**Delivered** / **Failed**).

Click into a delivery to see the full **request** (URL, headers including the signature, JSON body) and the full **response** (status, headers, body, duration). JSON is syntax-highlighted with a theme picker.

If a delivery failed, the detail view exposes a **Resend** button that re-enqueues that exact event for that exact subscription. Resending one delivery does **not** replay the event for other subscribers.

## Send a test event

Use **Send Test Event** from a row's action menu or the subscription detail page to fire a synthetic sample payload at your endpoint. Overflow POSTs the stub payload synchronously and displays the response (status, body, duration) inline, which is useful while wiring up a new integration.

Test events have two important properties:

* They include the header `x-overflow-webhook-test: true` and an event ID prefixed `evt_test_...`.
* They are **not retried** and **not stored** in the delivery log.

<Note>
  Use test events for sanity checks during integration. For audit-grade verification, trigger a real event and inspect it in the **Event Deliveries** tab.
</Note>

## Manage subscriptions

From the Webhooks list, each subscription row exposes an action menu:

* **Edit**: change the name, description, URL, or event selection. Edit cannot change a subscription's status; use Enable / Disable instead.
* **Enable / Disable**: pause or resume delivery without losing config or history.
* **Send Test Event**: fire a synthetic payload to validate the endpoint.
* **Delete**: soft-delete the subscription. It is archived rather than destroyed; it no longer receives events and is removed from the list.

<Warning>
  **Disabled subscriptions go silent.** Events that fire while a subscription is disabled are **not queued and not logged**; disabling is a pause-the-future toggle, not a save-for-later buffer.
</Warning>

## Quickstart

You can stand up a webhook integration end-to-end in a few minutes:

<Steps>
  <Step title="Stand up an HTTPS endpoint">
    Any server that accepts a JSON POST will work. For quick experiments, use a free inspector like [webhook.cool](https://webhook.cool) or RequestBin.
  </Step>

  <Step title="Create a subscription">
    In the dashboard, open **Developers → Webhooks**, click **+ New Subscription**, point it at your URL, and pick a small set of events.
  </Step>

  <Step title="Save the signing secret">
    Copy the secret shown at the end of the wizard and store it in your secret manager.
  </Step>

  <Step title="Send a test event">
    From the subscription detail page, click **Send Test Event** and confirm the payload reaches your endpoint.
  </Step>

  <Step title="Verify the signature">
    Implement HMAC-SHA256 verification against the `x-overflow-webhook-signature-hmacsha256` header using your saved secret.
  </Step>

  <Step title="Trigger a real event">
    Run a test contribution (or any subscribed event) and watch it flow through the **Event Deliveries** tab.
  </Step>
</Steps>

## Glossary

* **Webhook**: an HTTP POST that Overflow sends to your server when something happens in your account.
* **Subscription**: the configuration record you create: a URL, a list of events to listen for, a status, and a signing secret.
* **Endpoint / Destination URL**: the HTTPS URL that receives the POST.
* **Event**: a typed thing that happened (e.g., `contribution.approved`). Each event has a name and a JSON payload.
* **Wildcard**: the special event selection `*` meaning "everything, including future events."
* **Payload**: the JSON body of the webhook: includes the event type, an event ID, a timestamp, and the data for the resource.
* **Signing secret**: a random string unique to each subscription used to sign every outbound webhook.
* **HMAC-SHA256**: the cryptographic algorithm used to sign payloads.
* **Signature header**: `x-overflow-webhook-signature-hmacsha256`, the hex signature attached to each request.
* **Secret roll / rotation**: replacing the active signing secret with a new one, optionally with a grace window for dual-signing.
* **Grace window**: the period after a roll during which both the previous and new secrets sign each request.
* **Delivery**: a single HTTP attempt to a single subscription for a single event.
* **Delivery log**: the audit record of every delivery: request, response, status, timing, and attempt number.
* **Resend**: manually re-firing a previously-failed delivery from the dashboard.
* **Retry**: automatic re-attempt of a failed delivery by the queue worker on a backoff schedule.
* **Backoff**: progressively longer wait between retry attempts (over a 3-day window).
* **Test event**: a hand-crafted sample payload sent on demand from the dashboard so you can confirm an endpoint is wired up. Not logged, not retried.
* **Enabled / Disabled**: subscription status. Disabled subscriptions receive nothing and log nothing.
* **Degradation episode**: an open failing state for a subscription that starts when automated deliveries begin to fail and ends when Overflow disables the subscription (or the endpoint recovers). Drives the reminder email cadence.
* **Failing email / inactive email**: the two health notifications sent to the subscription creator. The **failing** email fires on reminder stages while the subscription is still enabled; the **inactive** email fires once Overflow auto-disables the subscription.
* **Terminal failure**: a delivery that will not be retried further — either a non-retriable HTTP response or a retriable failure that has exhausted the retry budget. Terminal failures trigger auto-disable and the inactive email.
