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

# Webhook events reference

> Every event Brimble's webhook delivery sends, with the exact payload shape your endpoint receives.

To configure delivery, see [Webhooks](/webhooks/overview).

## Envelope

Every webhook delivery is a `POST` with `Content-Type: application/json`. The body has this shape:

```typescript theme={null}
interface WebhookPayload<T> {
  event: WebhookEvent;
  data: T;            // shape depends on the event, see below
}
```

There is no `subscription_id`, no `timestamp`, no `project` field at the top level. Resource information lives inside `data`.

## Common types

These are referenced from multiple events.

```typescript theme={null}
type ID = string;
type ISODate = string;

type ServiceType = "web-service" | "static" | "worker" | "database" | "mcp";

type ProjectStatus =
  | "PENDING"
  | "INPROGRESS"
  | "ACTIVE"
  | "FAILED"
  | "CANCELLED"
  | "INACTIVE"
  | "DEGRADED"
  | "PAYMENT";

type Environment = "PRODUCTION" | "PREVIEW";

type GitType = "github" | "gitlab" | "bitbucket" | "docker";

interface Repo {
  name: string;
  full_name: string;
  id: number;
  branch: string;
  installationId: number;
  git: GitType;
}

interface Compute {
  cpu: number;          // vCPU (fractional)
  memory: number;       // GB
  storage: number;      // GB
}

interface Region {
  id: ID;
  name: string;
  continent: string;
}
```

## Deployment events

Sent for `deployment.success` and `deployment.failed`.

```typescript theme={null}
{
  event: "deployment.success" | "deployment.failed";
  data: {
    name: string;            // branch name
    status: ProjectStatus;
    user_id: ID;
    project_id: ID;
    team_id: ID | null;
    type: ServiceType;
    git: Repo | null;
    compute: Compute;
    message: string;         // commit message
  };
}
```

## Project events

Sent for `project.created`, `project.updated`, and `project.deleted`.

```typescript theme={null}
{
  event: "project.created" | "project.updated" | "project.deleted";
  data: {
    name: string;
    user_id: ID;
    project_id: ID;
    team_id: ID | null;
    type: ServiceType;
    region: Region;
    git: Repo | null;
    compute: Compute;
    created_at: ISODate;
  };
}
```

## Project domain updated

Sent for `project.domain.updated`.

```typescript theme={null}
{
  event: "project.domain.updated";
  data: {
    name: string;
    user_id: ID;
    project_id: ID;
    team_id: ID | null;
    domain: { new: string };
    domain_id: ID;
    updated_at: ISODate;
  };
}
```

## Domain events

Sent for `domain.created`, `domain.purchased`, and `domain.renewed`.

```typescript theme={null}
{
  event: "domain.created" | "domain.purchased" | "domain.renewed";
  data: {
    domain: string;            // the domain name
    domain_id: ID;
    user_id: ID;
    project_id: ID;
    team_id: ID | null;
    registrar: "Brimble Inc";
    purchased: boolean;
    price: number;             // renewal price
    expires_at: ISODate;
    purchased_at?: ISODate;    // present on domain.purchased
    created_at?: ISODate;      // present on domain.created
  };
}
```

## DNS record events

Sent for `dns.record.created`, `dns.record.updated`, and `dns.record.deleted`.

```typescript theme={null}
{
  event: "dns.record.created" | "dns.record.updated" | "dns.record.deleted";
  data: {
    user_id: ID;
    domain: string;            // parent domain name
    domain_id: ID;
    team_id: ID | null;
    record: {
      id: ID;
      name: string;            // host portion
      type: string;            // "A" | "AAAA" | "CNAME" | "MX" | "NS" | "TXT" | "SRV" | "CAA"
      value: string;
      ttl: number;             // seconds
      isProxied?: boolean;
    };
    created_at?: ISODate;      // present on dns.record.created
    updated_at?: ISODate;      // present on dns.record.updated
  };
}
```

## Environment variable events

Sent for `environment.variables.added`, `environment.variables.updated`, and `environment.variables.deleted`.

```typescript theme={null}
{
  event: "environment.variables.added" | "environment.variables.updated" | "environment.variables.deleted";
  data: {
    user_id: ID;
    project_id: ID;
    team_id: ID | null;
    environment: Environment | string;
    variable: string;          // the variable name only — value is never sent
    created_at?: ISODate;
    updated_at?: ISODate;
  };
}
```

The variable's **value is not included** in the payload. Only the variable name. Webhook handlers can react to "this variable changed" without seeing the secret.

## Autoscaling events

Sent for `autoscaling.group.created`, `autoscaling.group.updated`, and `autoscaling.group.deleted`.

```typescript theme={null}
{
  event: "autoscaling.group.created" | "autoscaling.group.updated" | "autoscaling.group.deleted";
  data: {
    name: string;
    autoscaling_group_id: ID;
    user_id: ID;
    team_id: ID | null;
    status: boolean;             // active flag
    min_instances: number;
    max_instances: number;
    desired_instances: number;
    scaling_policy: {
      cpu_threshold: number;
      memory_threshold: number;
    };
    created_at?: ISODate;
    updated_at?: ISODate;
  };
}
```

## Subscription events

Sent for `subscription.created`, `subscription.upgraded`, `subscription.downgraded`, `subscription.renewed`, `subscription.renewal_failed`, `subscription.past_due`, `subscription.unpaid`, `subscription.paused`, `subscription.reactivated`, `subscription.canceled`, and `subscription.incomplete_expired`.

```typescript theme={null}
{
  event: `subscription.${string}`;
  data: {
    subscription: {
      id: ID;
      stripe_id: string;
      stripe_status: string;            // "active" | "past_due" | "unpaid" | ...
      plan_type: string;                // "free-plan" | "hacker-plan" | ...
      type: string;                     // subscription "name", usually "default"
      ends_at?: ISODate;                // on subscription.canceled
      previous_plan?: string;           // on subscription.upgraded
      current_plan?: string;            // on subscription.upgraded
      previous_price?: string;          // on subscription.downgraded
    };
    user: {
      id: ID;
      email: string;
    };
    team: { id: ID } | null;            // null for personal subscriptions
    cancellation_reason?: string | null;
    failure_reason?: string | null;
    invoice?: {
      stripe_invoice_id: string;
      amount_paid?: number;
      amount_due?: number;
      currency?: string;
      attempt_count?: number;
    };
  };
}
```

## Payment events

Sent for `payment.successful` and `payment.failed`.

```typescript theme={null}
{
  event: "payment.successful" | "payment.failed";
  data: {
    invoice: {
      stripe_invoice_id: string;
      amount_paid?: number;          // payment.successful
      amount_due?: number;           // payment.failed
      currency: string;              // "usd", "ngn", etc.
      status: string;                // "paid" | "open" | ...
      paid?: boolean;                // payment.successful
      attempt_count?: number;        // payment.failed
    };
    user: { id: ID; email: string };
    subscription: {
      id: ID;
      stripe_id: string;
      plan_type: string;
      stripe_status: string;
    };
    team: { id: ID } | null;
    failure_reason?: string | null;  // payment.failed
  };
}
```

## Subscribing

Pass the events you want when configuring a webhook, or `["*"]` to receive everything. Pass `[]` to disable delivery without removing the configuration.

## Delivery model

* **At-least-once.** A delivery may be retried after a failed response. Make handlers idempotent. Keying on `data.project_id` (or `data.domain_id`, etc.) plus `event` plus the relevant timestamp works for most resources.
* **Order is not guaranteed.** Two events from the same project can arrive out of order. Use `created_at` / `updated_at` from `data` to reconcile.
* **Best-effort.** After repeated delivery failures, a single delivery is dropped. Your webhook itself stays enabled and continues to receive subsequent events.
