# Guide to Bump.sh Webhooks: Features and Best Practices

Bump.sh hosts API reference documentation generated from OpenAPI and AsyncAPI definitions, and tracks how those definitions change from one deployment to the next. Its webhook does one job: when a deployment changes the structure of an API, Bump.sh posts the change to your URL. Teams use it to tell API consumers about a new version, to flag breaking changes in chat, or to start regenerating SDKs in CI.

Because there is only one kind of notification, this is a short guide. It covers what the body contains, where to find the secret token, how to verify `X-Bump-Signature-256`, and the inconsistency in Bump.sh's documentation of that header.

## What are Bump.sh webhooks?

Webhooks are set up per documentation in Bump.sh. Each documentation can send to several URLs, and each webhook has its own secret token. Bump.sh sends a JSON `POST` to every URL whenever it notices that a new deployment has changed the API's structure.

There is no event name in the request. Every delivery is a structure change, so your receiver does not need to route by type.

## Bump.sh webhook features

| Feature | Details |
| --- | --- |
| Configuration | Documentation's **Integrations** tab in the dashboard |
| Trigger | A deployment that changes the API's structure |
| Signature | hex HMAC-SHA256 |
| Signature header | `x-bump-signature-256` |
| Header format | `sha256=<hex>` per the help page |
| Signed content | The raw request body |
| Secret | A secret token per webhook, on its confirmation screen |
| Body | JSON with `api` and `diff` |
| Delivery history | **Recent deliveries**, the last 10 notifications |
| Retries and timeout | Not documented |

## Events and payloads

| Notification | Triggered When | Common Use Cases |
| --- | --- | --- |
| API documentation structure change | A deployment changes the structure of a documentation | Post the change to chat, open a ticket, trigger SDK or client regeneration, notify API consumers |

Bump.sh's API reference describes the body as a schema rather than a complete example, so the samples library has no sample for it. The body has two objects:

* `api` describes the documentation: `id`, `name`, `description`, `slug`, `humanUrl`, `tags`, `version` and `properties`. `url` still appears but is deprecated. Each entry in `properties` has a `type`, such as OpenAPI or AsyncAPI, which describes the definition format, not the event.
* `diff` describes the change: `id`, `title`, `public_url`, `breaking`, `details`, `previous_version_url` and `current_version_url`.

`diff.breaking` is the field most receivers act on, and `diff.public_url` links to Bump.sh's human-readable changelog entry. After verifying the signature, still check the type of each field before reading it, since you have no full example to test against.

## Setting up Bump.sh webhooks

1. Open the documentation in the Bump.sh dashboard and select the **Integrations** tab.
2. Add a webhook with your payload URL, for example `https://your-app.example.com/webhooks/bump-sh`. Repeat for any other URLs.
3. Bump.sh shows a confirmation screen where you can change the payload URL and copy the **secret token**. Store the token on your server, for example as `BUMP_WEBHOOK_SECRET`. Use it as plain text, without decoding it.
4. In the webhook's **Recent deliveries** section, use **Test Webhook** to send a test notification.

To remove a webhook later, select it in the Integrations section and confirm in the **Danger zone**.

If you route through Hookdeck, create a source with the Bump.sh source type, paste the secret token into its verification settings, and add the Hookdeck source URL as the payload URL. During development, `npx hookdeck-cli listen 3000 bump-sh --path /webhooks/bump-sh` forwards notifications to a local server with the [Hookdeck CLI](/docs/cli).

## Securing Bump.sh webhooks

Bump.sh's help page verifies the request by computing `"sha256=" + hexdigest(HMAC-SHA256(secret, body))` and comparing it with the `X-Bump-Signature-256` header. The verifier below strips the `sha256=` prefix if present, checks the remainder is 64 hex characters, and compares it with the HMAC of the raw body in constant time. It also accepts a bare hex value, which is what Bump.sh's API reference shows. The full steps are in the [Bump.sh webhooks help page](https://docs.bump.sh/help/api-change-management/webhooks/) and the [verification reference](https://github.com/hookdeck/webhook-skills/blob/main/skills/bump-sh-webhooks/references/verification.md) in the Bump.sh skill.

```javascript
const crypto = require('node:crypto');

function verifySignature(rawBody, headers, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
  const signature = headers['x-bump-signature-256'];
  if (typeof signature !== 'string') return false;
  const key = Buffer.from(secret, 'utf8');
  const message = rawBody;
  const candidates = [signature.startsWith('sha256=') ? signature.slice(7) : signature];
  const expected = crypto.createHmac('sha256', key).update(message).digest();
  return candidates.some((candidate) => {
    if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
    const received = Buffer.from(candidate, 'hex');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

```

With the signature checked, the handler reads `diff` and reacts to breaking changes:

```javascript
const express = require('express');

const app = express();

app.post('/webhooks/bump-sh', express.raw({ type: '*/*' }), async (req, res) => {
  const secret = process.env.BUMP_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500);
  if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);

  let parsed;
  try {
    parsed = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return res.sendStatus(400);
  const { api, diff } = parsed;
  try {
    if (!diff || typeof diff.id !== 'string') return res.sendStatus(200);

    if (diff.breaking === true) {
      await alertApiConsumers(api && api.name, diff.title, diff.public_url);
    }
    await recordApiChange(diff.id, api && api.slug, diff.current_version_url);
  } catch {
    return res.sendStatus(503);
  }

  res.sendStatus(200);
});

```

`alertApiConsumers` and `recordApiChange` stand in for your own notification and storage code.

Bump.sh signs no timestamp, so the signature never expires. Hookdeck's Bump.sh source checks the same HMAC on `x-bump-signature-256`, answers Bump.sh with `200`, and marks a request that fails the check as failed verification instead of returning `401`.

> **Make Bump.sh webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies `X-Bump-Signature-256`, keeps a history of every structure change well beyond Bump.sh's last 10, and delivers each one to as many destinations as you need.

## Bump.sh webhook limitations and pain points

### Two spellings of the signature header

**The Problem:** A verifier built from one page of Bump.sh's documentation rejects real requests.

**Why It Happens:** The help page's Ruby example reads the header Rack exposes as `HTTP_X_BUMP_SIGNATURE_256`, which is `X-Bump-Signature-256` on the wire, and expects a `sha256=` prefix. The API reference names the header `X_BUMP_SIGNATURE_256` and shows a bare hex example. Many proxies drop headers with underscores in their names.

**Workarounds:**

* Read `X-Bump-Signature-256`, and accept the digest with or without the `sha256=` prefix, as the sample above does.
* Send a test from **Recent deliveries** and look at the actual header before you go live.

**How Hookdeck Can Help:** Hookdeck records the exact headers of every request, so you can see which form Bump.sh sent without adding logging to your service.

### No documented retries

**The Problem:** If your endpoint is down during a deployment, you may never hear about that change.

**Why It Happens:** Bump.sh documents no retry policy, timeout or expected response, and the dashboard keeps only the last 10 deliveries.

**Workarounds:**

* After an outage, compare your records with the documentation's changelog in Bump.sh.
* Return `200` quickly and do the slow work, such as SDK regeneration, afterwards.

**How Hookdeck Can Help:** Hookdeck stores each notification as soon as Bump.sh sends it and retries your service until it succeeds, with the full history available for replay.

### No delivery ID

**The Problem:** You can't tell a second delivery of the same change from a new one using headers alone.

**Why It Happens:** Bump.sh documents no delivery ID or timestamp header.

**Workarounds:**

* Use `diff.id` as the idempotency key. It identifies the change itself.

**How Hookdeck Can Help:** Hookdeck's deduplication can drop requests with a `diff.id` it has already seen.

## Best practices

### Verify the raw bytes

Compute the HMAC over the body exactly as received. In Express, use `express.raw({ type: '*/*' })`. In Next.js, use `await request.arrayBuffer()`, and in FastAPI, `await request.body()`.

### Use `diff.breaking` to decide urgency

Most structure changes are additive. Send breaking ones to people who need to act, and log the rest.

### Keep the secret per webhook

Each URL has its own secret token. If several services receive Bump.sh notifications, give each one its own webhook and secret rather than sharing one.

### Deduplicate on `diff.id`

Store `diff.id` with the work it triggered so that a repeated notification does nothing. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Conclusion

Bump.sh sends a single kind of webhook, a structure change with `api` and `diff` objects, signed with a hex HMAC-SHA256 of the body. Accept the signature with or without its `sha256=` prefix, key your processing on `diff.id`, and plan for the fact that Bump.sh documents neither retries nor a delivery ID.

[Hookdeck Event Gateway](https://hookdeck.com) verifies each Bump.sh notification and keeps it until your service has handled it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Bump.sh webhooks reliably in minutes.