Skip to main content

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.

To configure delivery, see Webhooks.

Envelope

Every webhook delivery is a POST with Content-Type: application/json. The body has this shape:
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.
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.
{
  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.
{
  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.
{
  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.
{
  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.
{
  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.
{
  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.
{
  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.
{
  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.
{
  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.
Last modified on May 9, 2026