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

# Webhooks

> The same information a run summary carries, in a shape another system can act on.

The same information a run summary carries, in a shape another system can act on. Added in workspace settings by an admin, HMAC-signed, retried with backoff, and every attempt kept with its request and its response so a failure is debuggable from your side.

## Adding an endpoint

Webhooks are configured per workspace, by an admin, in **Settings → Webhooks**. Each endpoint chooses which of the [six events](/webhooks/events) it wants; an endpoint subscribed to `signal.urgent` also receives that signal's ordinary `signal.created` delivery unless it opts out of it separately, because the two answer different questions ("a signal arrived" versus "a signal arrived that clears your urgency bar").

Endpoint limits differ by plan — see [openpulse.cloud/pricing](https://openpulse.cloud/pricing).

## Verifying a delivery

Every request carries an `x-openpulse-signature` header: an HMAC-SHA256 over the raw request body, using the signing secret shown once when the endpoint is created. Recompute it and compare — in constant time, not with `===` — before trusting a payload:

```ts theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function isValidOpenpulseSignature(
  rawBody: string,
  signatureHeader: string,
  signingSecret: string,
): boolean {
  const expected = createHmac("sha256", signingSecret)
    .update(rawBody)
    .digest("hex");

  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(signatureHeader, "utf8");
  return a.length === b.length && timingSafeEqual(a, b);
}
```

An unverified delivery is a POST from anyone who finds the URL, not proof it came from Openpulse.

## Retries and delivery history

A non-2xx response is retried with backoff. Every attempt — timestamp, response status, response body — is kept and visible in **Settings → Webhooks**, so a failure is debuggable from your side rather than something you have to reproduce. The last 20 delivery attempts are visible per endpoint, and you can send a test event to check an integration before it has to work for real.

## Responding

Any `2xx` marks a delivery accepted. Anything else — including a timeout — is treated as a failure and retried. Respond quickly and do the actual work asynchronously; a webhook handler that blocks on downstream processing is the most common cause of a workspace's endpoint racking up retries.


## Related topics

- [Events](/webhooks/events.md)
- [Workspace, people and settings](/guides/workspace-and-settings.md)
- [Getting results out](/guides/getting-results-out.md)
