> If you are setting up, configuring, or answering questions about Schematic, connect the
> Schematic MCP server before working from these pages. It reads and writes plans, features,
> entitlements, and overrides against the real account, so what you report and what you build
> match what actually exists.
>
> The server is at https://api.schematichq.com/mcp
>
> From Claude, ChatGPT, or another chat assistant, add it as a custom connector:
> https://docs.schematichq.com/working-with-ai
>
> From a coding assistant such as Claude Code, Codex, or Cursor:
> https://docs.schematichq.com/building-with-ai
>
> For clean Markdown of any page, append `.md` to the page URL. For a complete page index,
> see https://docs.schematichq.com/llms.txt

# Webhooks

> Subscribe to Schematic's company, catalog, billing, and usage trigger events, with the SDK payload type for each.

Webhooks allow you to listen to triggers from Schematic to create alerts or keep external services up to date when data changes.

## Supported webhook events

The table below lists every supported event and links to its payload type in the Node.js SDK. See [Payload types](#payload-types) below for links to other SDKs.

**Company & User events**

| Event                                                                                                          | Body type                                                                                                                                                                                     |
| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `company.created`, `company.updated`, `company.deleted`                                                        | [`CompanyDetailResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/CompanyDetailResponseData.ts)                                                             |
| `company.plan_changed`                                                                                         | [`PlanChangeResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/PlanChangeResponseData.ts) — see [Plan Changed Webhook](/integrations/webhooks/plan-changed) |
| `company.override.created`, `company.override.updated`, `company.override.deleted`, `company.override.expired` | [`CompanyOverrideResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/CompanyOverrideResponseData.ts)                                                         |
| `user.created`, `user.updated`, `user.deleted`                                                                 | [`UserDetailResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/UserDetailResponseData.ts)                                                                   |

**Catalog events**

| Event                                                                              | Body type                                                                                                                             |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `feature.created`, `feature.updated`, `feature.deleted`                            | [`FeatureDetailResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/FeatureDetailResponseData.ts)     |
| `flag.created`, `flag.updated`, `flag.deleted`                                     | [`FlagDetailResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/FlagDetailResponseData.ts)           |
| `flag_rules.updated`, `rule.deleted`                                               | [`RuleDetailResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/RuleDetailResponseData.ts)           |
| `plan.created`, `plan.updated`, `plan.deleted`                                     | [`PlanDetailResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/PlanDetailResponseData.ts)           |
| `plan_version.deleted`                                                             | [`PlanVersionResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/PlanVersionResponseData.ts)         |
| `plan.entitlement.created`, `plan.entitlement.updated`, `plan.entitlement.deleted` | [`PlanEntitlementResponseData`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/PlanEntitlementResponseData.ts) |

**Billing events**

| Event                      | Body type                                                                                                                     |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `subscription.trial.ended` | [`BillingSubscriptionView`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/BillingSubscriptionView.ts) |

For usage-based trigger events, see [Entitlement & Credit Trigger Webhooks](/integrations/webhooks/entitlement-triggers).

| Event                                                                                                                                                                                            | Body type                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `entitlement.limit.reached`, `entitlement.limit.warning`, `entitlement.soft_limit.reached`, `entitlement.soft_limit.warning`, `entitlement.tier_limit.reached`, `entitlement.tier_limit.warning` | [`WebFeatureUsageWebhookOutput`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/WebFeatureUsageWebhookOutput.ts) |
| `credit.limit.reached`, `credit.limit.warning`                                                                                                                                                   | —                                                                                                                                       |
| `credit.purchase.success`                                                                                                                                                                        | [`CreditsCreditPurchaseSuccess`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/CreditsCreditPurchaseSuccess.ts) |
| `auto.topup.hard.failure`                                                                                                                                                                        | [`CreditsAutoTopupHardFailure`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/CreditsAutoTopupHardFailure.ts)   |
| `auto.topup.retry.exceeded`                                                                                                                                                                      | [`CreditsAutoTopupRetryFailure`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/CreditsAutoTopupRetryFailure.ts) |
| `auto.topup.success`                                                                                                                                                                             | [`CreditsAutoTopupSuccess`](https://github.com/SchematicHQ/schematic-node/blob/main/src/api/types/CreditsAutoTopupSuccess.ts)           |

## Payload types

All webhook event payloads are strongly typed in each of our SDKs. The Node.js types are linked in the table above. The same type names are available in [Go](https://github.com/SchematicHQ/schematic-go/blob/main/types.go), [C#](https://github.com/SchematicHQ/schematic-csharp/tree/main/src/SchematicHQ.Client/Types), [Java](https://github.com/SchematicHQ/schematic-java/tree/main/src/main/java/com/schematic/api/types), and [Python](https://github.com/SchematicHQ/schematic-python/tree/main/src/schematic/types).

## Webhook structure

Each webhook from Schematic is a `POST` request with a JSON body structured as follows.

```json
{
  "action": "<webhook.name>",
  "account_id": "acct_xxxxxxx",
  "environment_id": "env_xxxxxxxx",
  "event_id": "whe_xxxxxxxx",
  "object_type": "<object>",
  "body": {},
  // if applicable
  "company_id": "comp_xxxxxxxx",
  "feature_id": "feat_xxxxxxxx"
}
```

| Field            | Description                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `action`         | The webhook event type, e.g. `company.created`                                                                                        |
| `account_id`     | The account the event occurred in                                                                                                     |
| `environment_id` | The environment within the account the event occurred in                                                                              |
| `event_id`       | Identifies this event and endpoint pair, and stays the same across retries, so you can [deduplicate on it](#deduplicating-deliveries) |
| `object_type`    | The type of object the event relates to                                                                                               |
| `body`           | Payload containing event-specific data; shape varies by event type                                                                    |
| `company_id`     | Present on company-scoped and entitlement trigger events                                                                              |
| `feature_id`     | Present on entitlement trigger events                                                                                                 |

The contents of `body` vary depending on the event. You can inspect real payloads using free services like [Webhook.site](https://webhook.site) or the webhook log in the Schematic dashboard.

## Signature verification

Every webhook request Schematic sends includes three headers for verifying authenticity:

| Header                                  | Description                                            |
| --------------------------------------- | ------------------------------------------------------ |
| `X-Schematic-Webhook-Signature`         | HMAC-SHA256 signature of the request body, hex-encoded |
| `X-Schematic-Webhook-Signature-Version` | Signature version — currently `v1`                     |
| `X-Schematic-Webhook-Timestamp`         | Unix timestamp of when this attempt was sent           |

The signature is computed as:

```
HMAC-SHA256(secret, rawBody + "+" + timestamp)  →  hex-encoded
```

where `secret` is the webhook secret shown in the Schematic dashboard when you create the webhook endpoint.

Each SDK ships a built-in helper to handle verification for you:

**`Node.js`**

```typescript title="Node.js"
import { verifyWebhookSignature } from "@schematichq/schematic-typescript-node";

// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  try {
    verifyWebhookSignature(req, process.env.SCHEMATIC_WEBHOOK_SECRET, req.body);
  } catch (err) {
    return res.status(400).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString());
  // handle event...
  res.sendStatus(200);
});
```

**`Python`**

```python title="Python"
from schematichq.webhook_utils import verify_webhook_signature, WebhookSignatureError

# Flask example
@app.route("/webhook", methods=["POST"])
def webhook():
    try:
        verify_webhook_signature(request, secret=os.environ["SCHEMATIC_WEBHOOK_SECRET"])
    except WebhookSignatureError:
        return "Invalid signature", 400

    event = request.get_json()
    # handle event...
    return "", 200
```

**`Go`**

```go title="Go"
import "github.com/schematichq/schematic-go/webhooks"

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    err := webhooks.VerifyWebhookSignature(r, os.Getenv("SCHEMATIC_WEBHOOK_SECRET"))
    if err != nil {
        http.Error(w, "Invalid signature", http.StatusBadRequest)
        return
    }

    // handle event...
    w.WriteHeader(http.StatusOK)
}
```

> **Warning**
>
> Always use your framework's raw body middleware when verifying signatures. Parsed or re-serialized bodies will not match the original signature.

## Delivery and retries

Schematic delivers webhooks asynchronously. Your endpoint should acknowledge receipt by responding with any `2xx` status code within 5 seconds. Acknowledge first and do the work afterwards; a handler that processes the event before responding will time out under load.

If your endpoint responds with a non-`2xx` status (anything `300` or above, including `4xx`) or does not respond within 5 seconds, Schematic treats the delivery as failed and retries it. You get eight attempts spread across roughly 24 hours, so you can take an endpoint down for a deploy, or sleep through an outage, and still receive the event. Each attempt lands at the offset below from the first attempt, jittered by ±10% so a shared outage does not replay every event at the same instant.

| Attempt | Sent             |
| ------- | ---------------- |
| 1       | immediately      |
| 2       | 30 seconds later |
| 3       | 5 minutes later  |
| 4       | 30 minutes later |
| 5       | 2 hours later    |
| 6       | 5 hours later    |
| 7       | 10 hours later   |
| 8       | 24 hours later   |

Once the eighth attempt fails, Schematic does not deliver the event again. Disabling or deleting the endpoint also stops any retries still in flight for it, and re-enabling the endpoint does not resume them.

Every attempt sends the same body, so `event_id` stays stable across retries. The timestamp and the signature are fresh on each attempt, so verify each request on its own.

### Deduplicating deliveries

You can receive the same event twice, most often when your endpoint processes a request but its response is lost in transit and Schematic retries. Every attempt at one event for one endpoint carries the same `event_id` in the body and in the `X-Schematic-Webhook-Id` header, so record the ids you have handled and drop a repeat rather than running your handler again.

| Header                        | Description                                                          |
| ----------------------------- | -------------------------------------------------------------------- |
| `X-Schematic-Webhook-Id`      | The same value as `event_id` in the body, identical on every attempt |
| `X-Schematic-Webhook-Attempt` | Which attempt this request is, counting from `1`                     |

A retry can also land after newer events for the same object, so compare the timestamps in `body` when you care about order rather than trusting the order requests arrive in.

Test sends from the dashboard go out once, stay out of the webhook log, and carry no `event_id` or `X-Schematic-Webhook-Id`, so exercise your deduplication against a real event.

### Endpoint health

If an endpoint fails consistently, Schematic automatically deactivates it so it stops sending to a broken destination. Our threshold for deactivation is a failure rate of 90% or higher across at least 25 events in a rolling 24-hour window. A delivery counts toward that rate once it has failed three attempts, roughly half an hour in, so Schematic catches a broken endpoint without waiting out every event's full retry window. You can re-enable the endpoint from the webhooks page in the dashboard once the issue is resolved.

## Developer Tooling

On the webhooks page in Schematic, you can find a log of all webhooks Schematic has sent for your account. We will only show events that have an associated webhook configured.

Each event gets one row per endpoint, and that row is updated in place as attempts are made rather than adding a row per attempt. The **Attempts** column counts the HTTP requests made so far, and a row reads `retrying` between a failed attempt and the next one, then settles on `success` or `failure`.

![webhook-log](/_fern-img/6e8053fe345ac0f8c0e19208653072d2ff4269d2ccc9899d46bc2e1f40b5ce80.webp)