Skip to content

Webhooks

An outbound webhook sends events in your organization to an HTTPS endpoint you run, as they happen: a release was created, a patch was promoted or rolled back, a member’s role changed. Use one to post to a chat channel, update a changelog, feed an analytics pipeline, or stream the audit log into your SIEM without polling.

Each delivery is an HTTPS POST of one OCSF 1.9.0 API Activity event, signed with a secret only you and Shorebird hold.

Webhooks are available on the Business and Enterprise plans. Which events a webhook can send depends on the plan:

An enterprise can also have webhooks of its own, which send the events in the enterprise audit log: changes to the enterprise, and membership and access changes in its organizations. See Enterprise webhooks.

An organization’s Admins and Owners manage its webhooks. See Member roles.

  1. In the Shorebird console, open your organization’s Settings and select the Webhooks tab.
  2. Click Add webhook.
  3. Enter the Endpoint URL. It must be an https:// URL on port 443 with a public hostname: not an IP address, localhost, or a .local name.
  4. Optionally, add a Description, such as where the events go.
  5. Under Events, choose All available events or Selected events, and pick the events to send. Events Shorebird adds later aren’t included, even with All available events. Edit the webhook to subscribe to them.
  6. Click Add webhook, then copy the Signing secret. It’s shown only once. You need it to verify deliveries.

An organization can have up to 5 webhooks.

Each webhook in the list shows whether it’s Enabled, when Shorebird last tried to deliver to it, the HTTP status your endpoint returned, and how many deliveries in a row have failed.

  • Send test ping sends a test event to the endpoint. It’s available while the webhook is on.
  • The switch turns the webhook off and on. While it’s off, events aren’t sent to it, and they aren’t sent later when you turn it back on.

The ⋮ menu holds the rest:

  • Edit webhook changes the URL, description, or events.
  • Rotate secret replaces the signing secret. See Rotate the secret.
  • Delete webhook stops deliveries to the endpoint at once.

Enterprise Owners and Admins add enterprise webhooks from Enterprise in the sidebar: open its Security tab and use the Outbound webhooks card, the same way as an organization’s. An enterprise can also have up to 5 webhooks.

An enterprise webhook sends the events in the enterprise’s own audit log: changes to the enterprise, such as its domains, roles and policies; membership and access changes in every organization in the enterprise, such as members joining, role changes, and service account keys; apps being created, renamed, moved or deleted; and deployment freeze windows and overrides. It doesn’t send releases, patches or channels. For those, add a webhook to the organization.

Each delivery is one event, in the same format as the audit log. api.operation names the event, and metadata.uid is its unique ID.

These are the events a webhook can send on the Business plan. On Enterprise they’re among the rest of the audit log’s events, along with others such as patch.rollout, a patch’s rollout percentage being raised.

  • release.create: a release was created.
  • release.update: a release was updated, such as its status or notes.
  • release.delete: a release was deleted.
  • release_platform.production_set: a release’s platform was marked as in production or not, or set back to automatic.
  • patch.create: a patch was created.
  • patch.notes_update: a patch’s notes were changed.
  • patch.promote: a patch was promoted to the stable channel.
  • patch.channel_update: a patch was moved to a channel other than stable.
  • patch.rollback: a patch was rolled back.
  • patch.rollforward: a rolled-back patch was rolled forward.
  • channel.create: a channel was created.
  • channel.delete: a channel was deleted.

Some changes happen on a schedule rather than when someone acts. They’re recorded in the audit log, but aren’t sent to webhooks:

  • A patch approved to ship at a later time: its patch.promote, patch.channel_update or patch.rollforward when that time comes, or its patch_approval.cancel if it can no longer ship then.
  • patch.rollout_update, a rollout percentage raised by its schedule.

Send test ping sends one event with api.operation webhook.test and activity_id 99, signed like any other delivery. Use it to check that your endpoint is reachable and verifies signatures.

{
  "class_uid": 6003,
  "category_uid": 6,
  "activity_id": 99,
  "type_uid": 600399,
  "severity_id": 1,
  "status_id": 1,
  "time": 1792180800000,
  "metadata": {
    "version": "1.9.0",
    "uid": "3f2b6c1e-8d4a-4f6b-9a2e-7c5d1b0e4a93",
    "tenant_uid": "1234",
    "product": { "name": "Shorebird", "vendor_name": "Shorebird" }
  },
  "api": { "operation": "webhook.test" },
  "actor": {
    "user": {
      "name": "Shorebird",
      "type_id": 3,
      "type": "System",
      "org": { "uid": "1234" }
    }
  },
  "src_endpoint": { "name": "unknown" },
  "unmapped": {}
}

A ping to an enterprise webhook has no tenant_uid or org, and has the enterprise’s ID in unmapped.enterprise_id.

Each delivery is a POST with a JSON body and these headers, following the Standard Webhooks specification:

  • webhook-id: the delivery’s ID. It’s the same on every retry of the same event, so use it to drop duplicates. For an audit log event it’s msg_audit_ followed by the event’s metadata.uid, and for a test ping msg_ping_ followed by its metadata.uid.
  • webhook-timestamp: when this attempt was sent, in seconds since the Unix epoch.
  • webhook-signature: v1, followed by the base64 signature.
  • User-Agent: Shorebird-Webhook/1.0.

Respond with any 2xx status within 5 seconds. Shorebird doesn’t follow redirects, so a 3xx response counts as a failure. Do slow work after you respond.

An event can arrive more than once, and events can arrive out of order. Order them by time.

Check the signature on every delivery before you trust it. The signature is an HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the signing secret’s bytes: the part after whsec_, base64-decoded. Sign the body exactly as it arrived, before parsing it as JSON.

Any Standard Webhooks library does this for you. Without one, in Node.js:

import crypto from 'node:crypto';

// headers: the request's headers, with lowercase names.
// body: the raw request body, as a string.
function isFromShorebird(secret, headers, body) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signatures = headers['webhook-signature'];
  if (!id || !timestamp || !signatures) return false;

  // Refuse old or future deliveries, so a captured request can't be replayed.
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!(age <= 5 * 60)) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = crypto
    .createHmac('sha256', key)
    .update(`${id}.${timestamp}.${body}`)
    .digest();

  return signatures.split(' ').some((signature) => {
    const [version, value] = signature.split(',');
    if (version !== 'v1' || !value) return false;
    const actual = Buffer.from(value, 'base64');
    return (
      actual.length === expected.length &&
      crypto.timingSafeEqual(actual, expected)
    );
  });
}

Respond 401 to a delivery that fails the check. Shorebird doesn’t retry a 401, so that event isn’t delivered again, and it counts toward turning the webhook off.

Rotate secret creates a new signing secret and shows it once. Deliveries are signed with the new secret from then on, and the old one stops working at once. You only see the new secret after rotating, so deliveries sent before your endpoint has it fail the check. To keep those events, have your endpoint respond 503 to a delivery it can’t verify while you switch: Shorebird retries it for a few minutes, and each retry is signed with the new secret. Once the endpoint has the new secret, go back to responding 401.

A delivery fails when your endpoint doesn’t respond with a 2xx status within 5 seconds, or can’t be reached.

Shorebird retries a delivery that timed out, couldn’t connect, or got a 408, 429 or 5xx response, waiting longer between attempts, starting at 10 seconds, for up to 6 attempts over a few minutes. Any other response, such as a 400 or a 3xx, isn’t retried, and neither is a hostname that resolves to a private or reserved IP address. A test ping is sent once and isn’t retried.

The webhook’s list entry shows the last HTTP status or error, and how many deliveries in a row have failed. A delivery that is retried counts once, when its last attempt fails. Test pings count too: a failed one adds to the count.

After 25 failed deliveries in a row, Shorebird turns the webhook off, and the console shows Webhook automatically disabled. Events aren’t sent while it’s off. Fix the endpoint, then click Re-enable, which starts the count again from zero. While a webhook is on, a successful delivery, including a test ping, also resets the count.

Webhooks stay listed if your organization moves to a plan without them, and you can still turn them off or delete them. Shorebird stops sending events to them.

A webhook keeps the events it was saved with. After an upgrade from Business to Enterprise, edit a webhook to add the audit log events it should send.

A Business organization’s webhooks send only release, patch and channel events. If one of its webhooks also subscribes to other audit log events, such as after a move from Enterprise, the console shows Some events aren’t on your plan and Shorebird stops sending to it. Edit it to remove those events.

On Enterprise, the audit log records each change to a webhook: webhook_endpoint.create, webhook_endpoint.update (including turning it off or on), webhook_endpoint.delete and webhook_endpoint.secret_rotate. Shorebird turning a webhook off after failed deliveries isn’t recorded.