# Dispatch Dispatch is a webhook management platform that receives, processes, and delivers webhooks to Discord, Slack, Telegram, and custom HTTP endpoints. External services POST to your webhook endpoints, events are stored, optionally filtered and transformed, formatted with message templates, and delivered with automatic retry and backoff logic. ## How It Works ``` External Service → POST /hooks/:slug → API → Event Store → Delivery Workers → Formatter → Destination ``` 1. An external service (GitHub, Slack, or any custom service) sends a `POST` request to your unique webhook endpoint URL 2. Dispatch verifies the request signature, stores the event, and evaluates any configured filters 3. Events that pass filters are fanned out to all linked destinations 4. For each destination, Dispatch optionally transforms the payload, renders a message template, and delivers the formatted message 5. Failed deliveries are automatically retried with exponential backoff ## Key Features - **Webhook Ingestion** — receive webhooks from any service via unique endpoint slugs with HMAC signature verification - **Event Filtering** — filter events based on header or body field conditions before delivery - **Payload Transforms** — transform event payloads with JSONata expressions before formatting - **Message Templates** — render Discord embeds, Slack Block Kit messages, and Telegram HTML using a template syntax with full access to event data; supports per-platform variants in a single template - **Reliable Delivery** — automatic retries with configurable exponential backoff (up to 7 attempts by default) - **Multi-Destination Fan-Out** — route a single event to multiple destinations with per-link template and transform config - **Signature Verification** — supports GitHub (`X-Hub-Signature-256`), Slack (`X-Slack-Signature`), GitLab (`X-Gitlab-Token`), Linear (`Linear-Signature`), Stripe (`Stripe-Signature`), Jira/Confluence (JWT Bearer), PayPal, and custom (`X-Dispatch-Signature`) signatures - **Integrations** — first-class OAuth integrations for GitHub, GitLab, Bitbucket, Slack, Jira, Confluence, Linear, Stripe, and PayPal - **Dashboard** — full web UI for managing endpoints, destinations, templates, viewing events, and monitoring delivery metrics - **CLI Tunnel** — `dispatch listen ` forwards live webhook events to your local development server over a secure tunnel; no public URL required - **Organizations** — multi-tenant with role-based access control (owner, admin, developer, viewer) - **API Keys** — programmatic access via `dsp_*` prefixed API keys - **Encryption** — webhook URLs and OAuth tokens encrypted at rest --- # Quickstart This guide walks you through receiving your first webhook and delivering it to Discord. ## 1. Create an Organization After signing up and logging in, create an organization from the dashboard. Organizations are the top-level container for projects, members, and billing. ## 2. Create a Project Within your organization, create a new project. The project is where you'll manage your webhook endpoints, destinations, and templates. ## 3. Create a Webhook Endpoint (Source) Navigate to **Sources** and create a new endpoint. Dispatch will generate: - A unique **slug** — your webhook URL will be `https://your-api.com/hooks/:slug` - A **signing secret** — used to verify incoming requests Copy the signing secret and configure it in your external service. ## 4. Create a Destination Navigate to **Destinations** and create a Discord destination: - **Manual:** Paste a Discord webhook URL directly - **OAuth:** Connect your Discord account and select a channel ## 5. Link Endpoint to Destination In the **Sources** page, link your endpoint to your destination. You can optionally assign a message template. ## 6. Send a Test Event Use the **Test** button on your endpoint to send a test event, or POST to your endpoint: ```bash # Using signing secret SIGNATURE=$(echo -n '{"test": true}' | openssl dgst -sha256 -hmac "your-signing-secret" | cut -d' ' -f2) curl -X POST https://your-api.com/hooks/your-slug \ -H "Content-Type: application/json" \ -H "X-Dispatch-Signature: sha256=$SIGNATURE" \ -d '{"test": true}' ``` ```bash # Using API key curl -X POST https://your-api.com/hooks/your-slug \ -H "Content-Type: application/json" \ -H "Authorization: Bearer dsp_your-api-key" \ -d '{"test": true}' ``` ## 7. Verify Delivery Check the **Events** page to see your event and its delivery status. The event should show as `received` with a successful delivery attempt to your Discord channel. ## Next Steps - [Forward events to your local machine](/getting-started/cli) using the Dispatch CLI - [Configure filters](/concepts/filters) to control which events are delivered - [Create templates](/concepts/templates) to customize Discord message formatting - [Set up transforms](/concepts/transforms) to reshape payloads before delivery - [Connect GitHub](/integrations/github) or [Slack](/integrations/slack) for automated endpoint setup --- # Local Development with the Dispatch CLI The `dispatch` CLI lets you forward incoming webhook events to a server running on your local machine. This means you can receive real webhooks from GitHub, Stripe, or any other service without exposing a public URL — perfect for local development and testing. ## How It Works When `dispatch listen` is running, events arriving at your Dispatch endpoint are forwarded over a secure tunnel to `http://localhost:`. Your local server handles the request and returns a response. The response is recorded and visible in the **Events** tab of the dashboard, giving you a full audit trail even during local development. Events are only forwarded while the CLI is connected. Events that arrive when the CLI is not running are not buffered — reconnecting starts forwarding new events from that point onward. ## Installation ### Via npm (recommended) ```bash npm install -g @dispatch.tech/cli ``` Requires Node.js 18 or later. Pre-compiled binaries for Linux, macOS, and Windows are downloaded automatically — no Go toolchain needed. ### Via Go ```bash go install github.com/dispatch-services/dispatch/packages/cli/cmd/dispatch@latest ``` Ensure `$GOPATH/bin` is on your `PATH`. ## Authenticate ```bash dispatch login ``` You will be prompted for your Dispatch email and password. To use GitHub or Google instead: ```bash dispatch login --oauth ``` Your credentials are never stored. Only the session tokens are saved to `~/.dispatch/config.json` (file permissions `0600`). ## Start Listening ```bash dispatch listen 3000 ``` An interactive picker lets you choose your organization, project, and webhook source. Once connected, the terminal shows a live stream of forwarded events: ``` Listening on my-github-source → localhost:3000/ Connected. → POST push 200 42ms → POST pull_request 200 11ms ``` ### Specify a source directly ```bash dispatch listen 3000 my-github-source ``` ### Forward to a specific path ```bash dispatch listen 3000 my-github-source --path /api/webhooks ``` Events are POSTed to `http://localhost:3000/api/webhooks`. ### Skip the project picker (useful in scripts) ```bash dispatch listen 3000 my-source-slug --project proj_abc123 ``` ## Keyboard Shortcuts While Listening | Key | Action | |-----|--------| | `r` | Replay the last received event | | `p` | Pause / resume forwarding | | `c` | Change source, transform, template, or delay | | `h` | Print keyboard shortcut help | | `q` / `Ctrl+C` | Exit | ## Interactive Picker The picker walks through org → project → source. Press `Esc` or `Backspace` to go back one level. If you have only one org and one project, those steps are skipped automatically. After selecting a source, you are offered optional settings: - **Transform** — apply a named JSONata transform to the payload before it is forwarded - **Template** — apply a message template - **Delivery delay** — introduce a deliberate delay before forwarding (useful for testing timeout handling) ## CLI Destination in the Dashboard Every `dispatch listen` session creates (or reuses) a **CLI destination** in your project. It appears on the **Destinations** page as: ``` CLI · alice@my-laptop · my-source-slug ``` This is how Dispatch knows where to forward events. You can safely ignore or delete these destinations — the CLI recreates them automatically on next run. ## Configuration File The CLI stores its state at `~/.dispatch/config.json`. You can inspect the `api_url` field to confirm which Dispatch instance you are connected to. ## Common Error Messages | Message | What to do | |---------|-----------| | `not logged in — run 'dispatch login' first` | Run `dispatch login`. | | `invalid port: ` | Use a number between 1 and 65535. | | `could not reach Dispatch API at ` | Check your network or `--api-url`. | | `no sources found` | Create a webhook source in the dashboard first. | | `refresh failed` | Your session expired — run `dispatch login` again. | --- # Webhook Endpoints (Sources) A webhook endpoint is the entry point for incoming webhooks. Each endpoint has a unique slug that forms its URL: `POST /hooks/:slug`. ## Adding a Source When you add a source, Dispatch generates: - **Slug** — a URL-safe identifier (e.g., `my-github-webhooks`) - **Signing Secret** — an HMAC secret for verifying request authenticity ### Configuration Options | Field | Description | |-------|-------------| | **Name** | Display name for the endpoint | | **Slug** | URL path segment (auto-generated, can be customized) | | **Signing Secret** | Secret used to verify incoming request signatures | | **Event Type Path** | Dot-notation path to extract event type from the payload body (e.g., `action` or `event.type`) | | **Provider** | Optional: `github`, `gitlab`, `bitbucket`, `slack`, `jira`, `linear`, or `stripe` for provider-specific behavior | | **Is Active** | Toggle to enable/disable the endpoint | ## Event Type Extraction Dispatch determines the event type from (in order of priority): 1. `X-GitHub-Event` header (GitHub webhooks) 2. `X-Gitlab-Event` header (GitLab webhooks) 3. `X-Event-Type` header (custom header) 4. Slack event type from payload structure (`event.type` in event_callback payloads) 5. Linear event type from `type` field in the request body 6. Stripe event type from `type` field in the request body (e.g., `payment_intent.succeeded`) 7. The endpoint's configured `event_type_path` (dot-notation path into the JSON body) The event type is stored with the event and can be used for filtering destinations by event type. ## Signature Verification All incoming requests must be authenticated using one of these methods: ### HMAC Signature (Default) Include an HMAC-SHA256 signature of the request body in one of these headers: - `X-Hub-Signature-256` (GitHub-compatible) - `X-Dispatch-Signature` Format: `sha256=` ```bash # Generate signature SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | cut -d' ' -f2) curl -X POST https://api.dispatch.tech/hooks/my-slug \ -H "Content-Type: application/json" \ -H "X-Dispatch-Signature: sha256=$SIGNATURE" \ -d "$BODY" ``` ### Slack Signature For Slack endpoints (provider = `slack`), Dispatch verifies the `X-Slack-Signature` header using the format: ``` v0:{timestamp}:{body} ``` The timestamp must be within 5 minutes of the current time to prevent replay attacks. ### GitLab Token For GitLab endpoints (provider = `gitlab`), Dispatch checks the `X-Gitlab-Token` header for a literal match against the endpoint's signing secret: ``` X-Gitlab-Token: your-signing-secret ``` ### Linear Signature For Linear endpoints (provider = `linear`), Dispatch verifies the `Linear-Signature` header, which contains an HMAC-SHA256 hex digest of the request body: ``` Linear-Signature: ``` ### Stripe Signature For Stripe endpoints (provider = `stripe`), set the endpoint's signing secret to your Stripe webhook signing secret (begins with `whsec_`). Dispatch verifies the `Stripe-Signature` header: ``` Stripe-Signature: t=...,v1=... ``` To get the signing secret, copy the **Webhook signing secret** from your Stripe Dashboard webhook endpoint configuration. ### Jira / Confluence JWT For Jira endpoints (provider = `jira`), Dispatch verifies the `Authorization: Bearer ` header included automatically by Jira when sending webhooks. When connecting via OAuth, Dispatch manages this automatically. ### API Key Include a project API key in the `Authorization` header: ``` Authorization: Bearer dsp_your-api-key ``` ## Endpoint-Destination Links An endpoint can be linked to multiple destinations. Each link can have: - An optional **message template** for formatting - **Routing rules** for conditional template selection - A **JSONata transform** applied before formatting See [Destinations](/concepts/destinations), [Templates](/concepts/templates), and [Transforms](/concepts/transforms) for details. ## Regenerating the Signing Secret You can regenerate an endpoint's signing secret from the dashboard. This immediately invalidates the old secret — update your external service configuration before the next webhook is sent. ## Deactivating an Endpoint Setting an endpoint to inactive causes all incoming requests to that slug to return `404 Not Found`. Existing events and delivery history are preserved. --- # Destinations A destination is a target where events are delivered. Dispatch supports five destination types: | Type | Description | |------|-------------| | **Discord** | Delivers formatted embeds to a Discord channel via webhook | | **Slack** | Delivers formatted messages to a Slack channel | | **Telegram** | Delivers formatted messages to a Telegram channel or group via bot | | **Webhook** | Sends the payload as an HTTP POST to any URL | | **CLI** | Forwards events to a local development server via the Dispatch CLI tunnel | ## Creating a Destination ### Discord You can create a Discord destination in two ways: - **Manual:** Paste a Discord webhook URL directly - **OAuth:** Connect your Discord account, select a server and channel, and Dispatch creates the webhook for you ### Slack Connect your Slack workspace via OAuth, then select a channel. ### Telegram Dispatch delivers Telegram messages via a bot. To create a Telegram destination: 1. Add the Dispatch bot to your target channel or group 2. In the dashboard, create a destination with type **Telegram** 3. Enter the **chat ID** — this can be a channel username (e.g., `@mychannel`), a numeric group/channel ID (e.g., `-1001234567890`), or a user ID for direct messages Message formatting defaults to auto-converting Discord embeds to Telegram HTML. You can also configure a native `telegram_html` template field for full control — see [Message Templates](/concepts/templates). ### Custom Webhook Enter any HTTP URL. Dispatch will POST the event payload (optionally transformed and formatted) to that URL. ## Configuration | Field | Description | |-------|-------------| | **Name** | Display name | | **Type** | `discord`, `slack`, `telegram`, or `webhook` | | **Webhook URL** | Target URL (encrypted at rest) | | **Channel ID** | Telegram chat, channel, or group ID (Telegram only) | | **Event Types** | Optional list of event types to accept (empty = all) | | **Is Active** | Toggle to enable/disable delivery | | **Retry Policy** | Retry configuration (see below) | ## Event Type Filtering You can configure a destination to only accept specific event types. If the `event_types` list is set, only events matching one of the listed types will be delivered to this destination. If the list is empty, all events are accepted. This is different from [endpoint filters](/concepts/filters) — event type filtering on destinations is a simple include list, while endpoint filters support complex conditional logic. ## Retry Policy Each destination has a configurable retry policy: ```json { "max_attempts": 7, "backoff": [1, 5, 30, 120, 900, 3600, 21600] } ``` | Field | Description | |-------|-------------| | `max_attempts` | Total number of delivery attempts (including the first) | | `backoff` | Array of delays in seconds between attempts | Default: 7 attempts with backoff of 1s, 5s, 30s, 2min, 15min, 1hr, 6hr. ### Retry Flow 1. First delivery attempt fails 2. A `retrying` delivery attempt is created with `next_retry_at` set based on the backoff schedule 3. The retry scheduler (polls every 5 seconds) picks up due retries 4. If the retry succeeds, the attempt is marked `success` 5. If the retry fails and more attempts remain, a new retry is scheduled 6. After `max_attempts`, the delivery is marked `failed` and moved to the dead-letter queue (DLQ) ### Rate Limit Handling For Discord, Slack, and Telegram destinations, Dispatch automatically handles `429 Too Many Requests` responses: - The `retry_after` value from the response is respected - A global cooldown is applied per provider when a global rate limit is detected - Rate-limited deliveries are rescheduled rather than counted as failures ## Linking to Endpoints Destinations are linked to endpoints to receive events. A single destination can be linked to multiple endpoints, and a single endpoint can fan out to multiple destinations. Each endpoint-destination link supports: - **Template** — which message template to use for formatting - **Routing Rules** — conditional template selection based on event fields - **Transform** — JSONata expression applied before formatting ## CLI Destination A CLI destination is created automatically when you run `dispatch listen` from the [Dispatch CLI](/getting-started/cli). It forwards incoming events to a server running on your local machine over a secure tunnel, and appears in the destinations list as: ``` CLI · alice@my-laptop · my-source-slug ``` Events are only forwarded while the CLI session is active. If the CLI is not running when a webhook arrives, the event is stored and visible in the **Events** tab, but it is not forwarded to that destination. See the [CLI guide](/getting-started/cli) for installation and usage instructions. ## Security - Webhook URLs and bot tokens are encrypted at rest - Credentials are only decrypted at delivery time and are never exposed in API responses --- # Events & Deliveries ## Events An event represents a single incoming webhook request. When an external service POSTs to your endpoint, Dispatch creates an event record with the full request payload and metadata. ### Event Fields | Field | Description | |-------|-------------| | `id` | Unique event ID (UUID) | | `endpoint_id` | The endpoint that received the webhook | | `organization_id` | The owning organization | | `event_type` | Extracted event type (e.g., `push`, `pull_request`) | | `payload` | The full JSON request body | | `headers` | All request headers as a JSON object | | `source_ip` | IP address of the sender | | `idempotency_key` | Optional key for deduplication | | `status` | `received` or `filtered` | | `created_at` | Timestamp of receipt | ### Event Status | Status | Description | |--------|-------------| | `received` | Event passed filters and was queued for delivery | | `filtered` | Event was blocked by endpoint filters (not delivered) | ### Idempotency If an event includes an idempotency key (via a header or payload field), Dispatch rejects duplicate events with `409 Conflict`. This prevents double-processing when a sender retries a webhook. ## Deliveries A delivery attempt records the result of sending an event to a destination. Each event can have multiple delivery attempts across multiple destinations, and each destination may have multiple retry attempts. ### Delivery Attempt Fields | Field | Description | |-------|-------------| | `id` | Unique attempt ID (UUID) | | `event_id` | The event being delivered | | `destination_id` | The target destination | | `attempt_number` | 1-based attempt counter | | `status` | Current status (see below) | | `status_code` | HTTP response status code | | `response_body` | First 2KB of the response body | | `response_headers` | Response headers as JSON | | `error` | Error message (if failed) | | `latency_ms` | Request round-trip time in milliseconds | | `next_retry_at` | When the next retry is scheduled (if retrying) | | `payload_sent` | The actual JSON payload that was delivered | | `created_at` | Timestamp of the attempt | ### Delivery Status | Status | Description | |--------|-------------| | `queued` | In the delivery queue, waiting for a worker | | `success` | Delivered successfully (2xx response) | | `failed` | Exhausted all retry attempts | | `retrying` | Waiting for the next retry window | | `canceled` | Manually canceled or superseded | ## Delivery Flow ``` Event Created → Fan-out to linked destinations → For each destination: → Apply transform (if enabled) → Render template (or auto-format) → POST to destination URL → Success (2xx)? → Mark "success" → Failure? → Schedule retry per backoff policy → All retries exhausted? → Mark "failed", move to DLQ ``` ## Replaying Events You can replay an event from the dashboard or API. Replaying re-queues the event for delivery to all linked destinations using the current endpoint-destination configuration (templates, transforms, routing rules). This is useful for: - Testing template changes against real event data - Re-delivering events after fixing a misconfigured destination - Recovering from destination outages ## Viewing Events in the Dashboard The Events page provides: - **Time range filtering** — 24h, 72h, 7d, 30d, or custom date range - **Status filtering** — all, received, or filtered - **Endpoint filtering** — filter by source endpoint - **Destination filtering** — filter by target destination - **Custom columns** — add columns for any JSON path in the payload or headers - **Suggested columns** — auto-recommendations based on common fields across events - **Event detail view** — full payload, headers, and all delivery attempts --- # Event Filters Filters allow you to control which events are delivered by evaluating conditions against the event's headers and payload body. Filtered events are stored with a `filtered` status and are not delivered to any destination. ## How Filters Work Filters are configured per **endpoint** (not per destination). They are evaluated during ingress, before any delivery jobs are created. ### Filter Groups Filters are organized into **groups**. Each group contains one or more **conditions** and a logic mode: - **AND** — all conditions in the group must match - **OR** — at least one condition in the group must match ### Group Evaluation Groups are combined with OR logic: an event passes if **any enabled group matches**. If no filter groups are defined for an endpoint, **all events pass** (no filtering occurs). ``` Group 1 (AND): condition A AND condition B Group 2 (OR): condition C OR condition D Event passes if: (A AND B) OR (C OR D) ``` ## Filter Conditions Each condition evaluates a field from the event against a value using an operator. ### Field Paths | Prefix | Description | Example | |--------|-------------|---------| | `header.` | Request header (case-insensitive) | `header.X-GitHub-Event` | | `body.` | JSON body field (dot-notation) | `body.action`, `body.repository.private` | Nested fields use dot notation: `body.pull_request.head.ref` ### Operators | Operator | Description | Example | |----------|-------------|---------| | `eq` | Field equals value | `body.action` eq `"push"` | | `neq` | Field does not equal value | `body.action` neq `"deleted"` | | `contains` | Field contains substring | `body.ref` contains `"main"` | | `not_contains` | Field does not contain substring | `body.ref` not_contains `"test"` | | `exists` | Field exists and is non-empty | `header.X-GitHub-Event` exists | | `not_exists` | Field does not exist or is empty | `body.draft` not_exists | | `regex` | Field matches regular expression | `body.ref` regex `"refs/heads/(main\|develop)"` | ## Examples ### Only accept push events to the main branch ``` Group (AND): - header.X-GitHub-Event eq "push" - body.ref eq "refs/heads/main" ``` ### Accept push or pull_request events ``` Group (OR): - header.X-GitHub-Event eq "push" - header.X-GitHub-Event eq "pull_request" ``` ### Accept all events except draft PRs ``` Group (AND): - header.X-GitHub-Event exists - body.pull_request.draft neq "true" ``` ### Complex: accept pushes to main OR non-draft PRs ``` Group 1 (AND): - header.X-GitHub-Event eq "push" - body.ref eq "refs/heads/main" Group 2 (AND): - header.X-GitHub-Event eq "pull_request" - body.pull_request.draft neq "true" ``` Event passes if it matches Group 1 OR Group 2. ## Filter vs. Destination Event Types Filters and destination event type lists serve different purposes: | Feature | Filters | Destination Event Types | |---------|---------|------------------------| | Scope | Per endpoint | Per destination | | Timing | Before delivery queue | During fan-out | | Complexity | Field conditions with operators | Simple include list | | Blocked events | Stored as `filtered`, visible in event history | Not delivered, no record | | Use case | Complex conditional logic | Simple event type routing | --- # Message Templates Message templates control how events are formatted before delivery. Templates support three output formats: Discord embeds, Slack Block Kit messages, and Telegram HTML. ## Template Formats Templates support two config versions: ### v1 — Flat (single platform) A single `TemplateConfig` object that applies to all platforms. This is the simpler format and is still fully supported: ```json { "title": "{{.Meta.EventType}}", "description": "New event from {{.Body.repository.full_name}}", "color": 16750848, "fields": [ { "name": "Action", "value": "{{.Body.action}}", "inline": true } ], "footer": "Dispatch", "thumbnail": "{{.Body.sender.avatar_url}}", "author": "{{.Body.sender.login}}", "timestamp": true } ``` ### v2 — Multi-platform A `PlatformTemplateConfig` envelope with per-platform variants. Dispatch selects the platform matching the destination type, falling back to `default` if no specific variant is configured: ```json { "version": 2, "platforms": { "discord": { "title": "{{.Meta.EventType}}", "description": "{{.Body.repository.full_name}}", "color": 5763719, "fields": [ { "name": "Action", "value": "{{.Body.action}}", "inline": true } ], "timestamp": true }, "slack": { "slack_blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*{{.Meta.EventType}}* — {{.Body.repository.full_name}}" } } ] }, "telegram": { "telegram_html": "{{.Meta.EventType}}\n{{.Body.repository.full_name}}\n\nAction: {{.Body.action}}" }, "default": { "title": "{{.Meta.EventType}}", "description": "{{.Body.repository.full_name}}", "timestamp": true } } } ``` ## Discord Embed Config The Discord platform config (or v1 flat config) renders a Discord embed: ### Fields | Field | Type | Description | |-------|------|-------------| | `title` | string | Embed title (max 256 chars). Supports template syntax. | | `description` | string | Main content (max 4096 chars, supports markdown). Supports template syntax. | | `color` | integer | Decimal color code (e.g., `16711680` for red, `65280` for green) | | `fields` | array | Up to 25 embed fields | | `footer` | string | Footer text. Supports template syntax. | | `thumbnail` | string | URL for thumbnail image. Supports template syntax. | | `author` | string | Author name. Supports template syntax. | | `timestamp` | boolean | Whether to include an ISO 8601 timestamp | ### Embed Fields Each field in the `fields` array has: | Field | Type | Description | |-------|------|-------------| | `name` | string | Field label (max 256 chars). Supports template syntax. | | `value` | string | Field content (max 1024 chars). Supports template syntax. | | `inline` | boolean | Whether to display inline (side-by-side with other inline fields) | ## Slack Block Kit Config For Slack destinations, set a `slack_blocks` array in the platform config. This renders natively as a Slack Block Kit message. All string values inside the blocks support template expressions: ```json { "slack_blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "{{upper .Meta.EventType}}" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Repository*\n{{.Body.repository.full_name}}" }, { "type": "mrkdwn", "text": "*Action*\n{{.Body.action}}" } ] }, { "type": "divider" } ] } ``` If `slack_blocks` is not set, the Discord embed fields are auto-converted to a Slack attachment format. ## Telegram HTML Config For Telegram destinations, set a `telegram_html` field in the platform config. This string supports template expressions and is sent as HTML. Supported HTML tags: | Tag | Effect | |-----|--------| | `` / `` | Bold | | `` / `` | Italic | | `` / `` | Underline | | `` / `` / `` | Strikethrough | | `` | Inline code | | `
` | Code block |
| `
` | Block quote | | `` | Hyperlink | | `` | Hidden spoiler | ```json { "telegram_html": "{{upper .Meta.EventType}} on {{.Body.repository.full_name}}\n\n{{.Body.pull_request.title}}\n\nOpened by: {{.Body.sender.login}}" } ``` If `telegram_html` is not set, Dispatch auto-converts the Discord embed fields to Telegram HTML. ## Template Variables All template fields have access to two objects: ### `.Body` The full event payload as a nested map. Access any field using dot notation: ``` {{.Body.action}} {{.Body.repository.full_name}} {{.Body.pull_request.title}} {{.Body.commits[0].message}} ``` ### `.Meta` Event metadata: | Variable | Description | |----------|-------------| | `{{.Meta.EventType}}` | The extracted event type (e.g., `push`, `pull_request`) | | `{{.Meta.EventID}}` | The unique event ID (UUID) | | `{{.Meta.EndpointID}}` | The endpoint that received the event | | `{{.Meta.Timestamp}}` | ISO 8601 timestamp of when the event was received | ## Template Functions The following functions are available in template expressions: | Function | Description | Example | |----------|-------------|---------| | `upper` | Convert to uppercase | `{{upper .Body.action}}` | | `lower` | Convert to lowercase | `{{lower .Body.action}}` | | `default` | Fallback value if empty | `{{default .Body.name "Unknown"}}` | | `truncate` | Truncate to N characters | `{{truncate .Body.description 100}}` | | `json` | Format value as JSON | `{{json .Body.commits}}` | | `ansiGray` | Discord ANSI gray color | `{{ansiGray "text"}}` | | `ansiRed` | Discord ANSI red color | `{{ansiRed "error"}}` | | `ansiGreen` | Discord ANSI green color | `{{ansiGreen "success"}}` | | `ansiYellow` | Discord ANSI yellow color | `{{ansiYellow "warning"}}` | | `ansiBlue` | Discord ANSI blue color | `{{ansiBlue "info"}}` | | `ansiPink` | Discord ANSI pink color | `{{ansiPink "text"}}` | | `ansiCyan` | Discord ANSI cyan color | `{{ansiCyan "text"}}` | | `ansiWhite` | Discord ANSI white color | `{{ansiWhite "text"}}` | | `ansiBold` | Discord ANSI bold | `{{ansiBold "text"}}` | | `ansiUnderline` | Discord ANSI underline | `{{ansiUnderline "text"}}` | > **Note:** ANSI color functions are only meaningful inside Discord code block descriptions (` ```ansi ``` `). They have no effect in Slack or Telegram output. ## Auto-Formatting When no template is assigned to an endpoint-destination link, Dispatch uses auto-detection based on the event payload to generate an appropriate embed. ### GitHub Events If the payload contains `repository` and `sender` fields, Dispatch generates a specialized embed based on the event type: - **push** — commit summary with author, branch, and commit messages - **pull_request** — PR title, description, status, and labels - **issues** — issue title, body, assignees, and labels - **release** — release name, tag, and description - **workflow_run** — workflow name, status, and conclusion - And more GitHub event types with tailored formatting ### GitLab Events If the payload contains a `project` object with a GitLab structure, Dispatch generates embeds for: - **Push Hook** — branch, commit count, and commit messages - **Merge Request Hook** — MR title, description, and status - **Issue Hook** — issue title and description ### Bitbucket Events For Bitbucket payloads (containing `repository.scm = "git"` and Bitbucket-style structure): - **repo:push** — branch and commit summary - **pullrequest:created / fulfilled / rejected** — PR title, status, and participants ### Jira Events For Jira payloads (`webhookEvent` field present): - **jira:issue_created** — issue key, summary, type, and priority - **jira:issue_updated** — field changes with old/new values - **jira:issue_deleted** — issue key and summary ### Linear Events For Linear payloads (`type` and `organizationId` fields present): - **Issue** lifecycle events — title, status, priority, and assignee - **Comment** events — comment text and issue context - **Project** updates — project name and status changes ### Stripe Events For Stripe payloads (`object` field with `"api_version"` present, or `livemode` field): - **charge.succeeded / failed** — amount, currency, and customer - **payment_intent.succeeded / payment_failed** — amount and status - **subscription.created / updated / deleted** — plan name and customer - **invoice.paid / payment_failed** — amount due and customer ### Slack Events If the payload contains `team_id`, `api_app_id`, and `type: event_callback`, Dispatch generates Slack-specific embeds for: - Messages, reactions, channel events, member events, pins, files, emoji changes, and more ### Default For unrecognized payloads, Dispatch creates a simple embed with the raw JSON payload formatted as a code block. ## Routing Rules Each endpoint-destination link can have routing rules that select different templates based on event fields: ```json [ { "field": "body.action", "op": "eq", "value": "opened", "template_id": "uuid-for-opened-template" }, { "field": "body.action", "op": "eq", "value": "closed", "template_id": "uuid-for-closed-template" } ] ``` Rules are evaluated top-to-bottom. The first matching rule's template is used. If no rule matches, the default link template (or auto-formatting) is used. A routing rule can also specify a `transform_id` to override the transform for that rule. ### Supported Operators | Operator | Description | |----------|-------------| | `eq` | Equals | | `neq` | Not equals | | `contains` | Contains substring | | `not_contains` | Does not contain substring | | `exists` | Field exists and is non-empty | | `not_exists` | Field does not exist or is empty | --- # Payload Transforms Transforms allow you to reshape the event payload before it is formatted and delivered. Transforms use [JSONata](https://jsonata.org/) expressions. ## How Transforms Work 1. An event is received and stored with its original payload 2. During delivery, if the endpoint-destination link has a `transform_id` set, the associated JSONata expression is evaluated against the original payload 3. The transformed result becomes the payload used for template rendering and delivery 4. If the transform fails for any reason, the **original payload is used** (fail-open behavior) To disable a transform on a link, remove the `transform_id` assignment. The original payload will pass through unchanged. ## Configuring Transforms Transforms are created and managed from the **Transforms** page in the dashboard, then assigned to endpoint-destination links by selecting them from the link editor. Each link can reference a different transform, allowing different destinations to receive different views of the same event. See [Dashboard: Transforms](/dashboard/transforms) for details. ## JSONata Examples ### Extract specific fields ```jsonata { "action": action, "repo": repository.full_name, "sender": sender.login, "url": pull_request.html_url } ``` ### Flatten nested structures ```jsonata { "commit_count": $count(commits), "branch": $substringAfter(ref, "refs/heads/"), "pusher": pusher.name, "messages": commits.message } ``` ### Conditional values ```jsonata { "status": action = "opened" ? "New" : action = "closed" ? "Done" : "Updated", "priority": $exists(labels[name = "critical"]) ? "high" : "normal" } ``` ### Array operations ```jsonata { "files_changed": $count(commits.($count(added) + $count(modified) + $count(removed))), "authors": $distinct(commits.author.name), "latest_message": commits[-1].message } ``` ## Fail-Open Behavior Transforms are designed to be safe: - If the JSONata expression has a syntax error, the original payload is used - If the expression produces `null` or `undefined`, the original payload is used - If the expression throws a runtime error, the original payload is used - Transform errors are logged but do not block delivery This ensures that a misconfigured transform does not prevent events from being delivered. ## Transform vs. Template | Feature | Transform | Template | |---------|-----------|----------| | Language | JSONata | Template expressions | | Purpose | Reshape payload structure | Format message for delivery | | Input | Raw event payload | Transformed payload (or original) | | Output | New JSON object | Discord embed / Slack Block Kit / Telegram HTML | | Timing | Before formatting | After transform | Transforms and templates work together: the transform reshapes the data, then the template formats it for display. --- # Organizations & Access Control ## Organizations Organizations are the top-level container in Dispatch. They group projects, members, and billing under a single entity. ### Creating an Organization After signing up, create an organization from the dashboard. You become the **owner** automatically. ### Organization Settings | Field | Description | |-------|-------------| | **Name** | Display name | | **Slug** | URL-safe identifier (auto-generated) | | **Plan** | Subscription tier: `free`, `team`, or `growth` | ## Member Roles Organizations use role-based access control with four roles: | Role | Permissions | |------|-------------| | **Owner** | Full access. Can delete org, transfer ownership, manage billing. One per org. | | **Admin** | Manage members, projects, and all resources. Cannot delete org or transfer ownership. | | **Developer** | Create and manage endpoints, destinations, templates. Cannot manage members. | | **Viewer** | Read-only access to events, deliveries, and metrics. | ### Role Hierarchy Owners > Admins > Developers > Viewers You can only invite members with a role lower than your own. Admins cannot invite other admins — only the owner can. ## Inviting Members 1. Navigate to the **Members** page in your organization 2. Enter the email address and select a role 3. The invitee receives an email with a link to accept 4. Invitations expire after a set period ### Member Limits | Plan | Member Limit | |------|-------------| | Free | 2 | | Team | 5 | | Growth | 25 | ## Ownership Transfer The organization owner can transfer ownership to another member: 1. The current owner selects a member to promote 2. The selected member becomes the new owner 3. The previous owner is demoted to admin ## Private Projects On paid plans, projects can be marked as **private**: - **Public projects** — accessible to all organization members - **Private projects** — only accessible to explicitly added members plus org owners and admins ### Adding Members to Private Projects 1. Go to **Project Settings** → **Privacy** 2. Enable private mode 3. Add members from the organization with a role (admin, developer, viewer) 4. Org owners and admins always have access regardless ## Usage & Billing ### Event Usage Each organization has an event limit based on its plan. Usage is tracked per billing period: - **Included Events** — events counted against the plan limit (received or filtered) - **Overage Events** — events exceeding the plan limit, allowed on paid plans and billed at period end The dashboard displays a usage bar showing included consumption and any overage against the plan limit. ### Overage Billing Team and Growth plans support overage: events past the included limit are still accepted and billed at **$0.03 per 1,000 events** at the end of the billing period. The Free plan hard-caps at 10,000 events with no overage. ### Plans | Feature | Free | Team | Growth | |---------|------|------|--------| | Price | $0 | $39/month | $399/month | | Events/month | 10,000 | 50,000 | 500,000 | | Team members | 2 | 5 | 25 | | Event history | 3 days | 7 days | 30 days | | Private projects | No | Yes | Yes | | Overage | No | $0.03/1k events | $0.03/1k events | | Support | Community | Priority | Dedicated | ### Subscription Management Billing is managed from the **Billing** page under your organization. From there you can: - View current plan and subscription status - See your estimated bill for the current period - Upgrade or change plans - Access the Stripe customer portal to update payment details or cancel --- # Authentication The Dispatch API supports two authentication methods depending on the endpoint being called. ## Webhook Ingress (`POST /hooks/:slug`) Incoming webhooks are authenticated using one of three methods: ### HMAC Signature Include an HMAC-SHA256 signature of the raw request body in one of these headers: | Header | Format | |--------|--------| | `X-Hub-Signature-256` | `sha256=` | | `X-Dispatch-Signature` | `sha256=` | | `X-Slack-Signature` | `v0=` (Slack-specific) | **GitHub / Custom signature:** ```bash BODY='{"action": "push"}' SECRET="your-signing-secret" SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2) curl -X POST https://api.dispatch.tech/hooks/my-slug \ -H "Content-Type: application/json" \ -H "X-Dispatch-Signature: sha256=$SIGNATURE" \ -d "$BODY" ``` **Slack signature:** The Slack signature uses the format `v0:{timestamp}:{body}` as the HMAC input. The timestamp must be within 5 minutes of the current time. ### API Key Include a project API key with `dsp_` prefix: ``` Authorization: Bearer dsp_abc123... ``` API keys are created in **Project Settings**. The full key is shown only once at creation time. ## Management API (`/v1/*`) All management endpoints require a user session JWT: ``` Authorization: Bearer eyJhbGciOiJSUzI1NiIs... ``` The JWT is validated on the server. The `sub` claim identifies the authenticated user. ### Getting a JWT JWTs are issued when a user signs in through the dashboard. The dashboard handles this automatically. For programmatic access, obtain a token through the Dispatch authentication flow. ## Error Responses | Status | Description | |--------|-------------| | `401 Unauthorized` | Missing, invalid, or expired authentication | | `403 Forbidden` | Valid auth but insufficient permissions | | `404 Not Found` | Resource not found or not accessible to the authenticated user | --- # Webhook Ingress The ingress endpoint is the main entry point for receiving webhooks from external services. ## POST /hooks/:slug Receive and process a webhook event. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `slug` | string | The unique endpoint slug | ### Headers | Header | Required | Description | |--------|----------|-------------| | `Content-Type` | Yes | Must be `application/json` | | `X-Hub-Signature-256` | Conditional | GitHub/Bitbucket HMAC-SHA256 signature (`sha256=` prefix) | | `X-Dispatch-Signature` | Conditional | Custom HMAC-SHA256 signature | | `X-Slack-Signature` | Conditional | Slack signature (with `X-Slack-Request-Timestamp`) | | `X-Gitlab-Token` | Conditional | GitLab secret token | | `Linear-Signature` | Conditional | Linear HMAC-SHA256 signature | | `Stripe-Signature` | Conditional | Stripe webhook signature (`t=` + `v1=`) | | `Authorization` | Conditional | Jira/Confluence JWT Bearer token, or `Bearer dsp_` | | `X-GitHub-Event` | No | GitHub event type (auto-detected) | | `X-Gitlab-Event` | No | GitLab event type (auto-detected) | | `X-Event-Type` | No | Custom event type header | At least one authentication header is required (signature or API key). ### Limits | Limit | Value | |-------|-------| | Maximum payload size | 10 MiB | | Rate limit | 100 requests/sec per IP | ### Request Body Any valid JSON object. ```json { "action": "push", "repository": { "full_name": "owner/repo" }, "sender": { "login": "username" } } ``` ### Response **202 Accepted** — Event received and queued for delivery. ```json { "event_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ### Error Responses | Status | Description | |--------|-------------| | `400 Bad Request` | Invalid JSON body | | `401 Unauthorized` | Invalid signature or API key | | `404 Not Found` | Endpoint not found or inactive | | `409 Conflict` | Duplicate event (idempotency key already used) | | `429 Too Many Requests` | Organization event limit reached | | `500 Internal Server Error` | Failed to store event | ### Processing Pipeline 1. **Endpoint lookup** — find active endpoint by slug 2. **Authentication** — verify signature or API key 3. **Payload validation** — ensure valid JSON 4. **Slack challenge** — if Slack URL verification, respond immediately 5. **Event type extraction** — from headers or payload path 6. **Header collection** — store all request headers 7. **Usage check** — verify org hasn't exceeded event limits 8. **Event storage** — persist the event 9. **Filter evaluation** — if blocked, mark as filtered and stop 10. **Fan-out** — create delivery jobs for all active linked destinations 11. **Queue** — enqueue jobs for async delivery ### Slack URL Verification When Slack sends a URL verification challenge, Dispatch responds immediately: ```json // Incoming { "type": "url_verification", "challenge": "abc123" } // Response (200 OK) { "challenge": "abc123" } ``` This is handled before event storage, so no event is created for verification requests. ### Response Headers | Header | Description | |--------|-------------| | `X-Request-ID` | Unique request ID for tracing | --- # Events API ## POST /v1/projects/:id/events/:eid/replay Replay an existing event by re-queuing it for delivery to all linked destinations. ### Authentication Requires JWT. The authenticated user must own the project. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Project ID | | `eid` | UUID | Event ID | ### Response **200 OK** ```json { "message": "event replayed", "destinations": 3 } ``` ### Error Responses | Status | Description | |--------|-------------| | `400 Bad Request` | Invalid project or event ID format | | `404 Not Found` | Project or event not found | | `500 Internal Server Error` | Failed to fetch destinations | | `503 Service Unavailable` | Delivery queue unavailable | ### Behavior - Uses the **current** endpoint-destination configuration (templates, transforms, routing rules) - Creates new delivery attempts — does not modify existing ones - The original event payload is used (transforms are re-applied at delivery time) --- ## POST /v1/projects/:id/sources/:eid/test Send a synthetic test event through an endpoint. ### Authentication Requires JWT. The authenticated user must own the project. The endpoint must belong to the project. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Project ID | | `eid` | UUID | Endpoint ID | ### Request Body (Optional) ```json { "payload": { "action": "test", "repository": { "full_name": "owner/repo" } } } ``` If no payload is provided, a default is used: ```json { "test": true, "message": "Hello from Dispatch!" } ``` ### Response **202 Accepted** ```json { "event_id": "550e8400-e29b-41d4-a716-446655440000", "destinations": 2 } ``` ### Behavior - Creates a real event with `event_type = "test"` - Event is stored and processed normally - Delivery is attempted to all active linked destinations - Useful for testing templates, transforms, and destination connectivity --- # Data Models This page documents the core data structures used throughout the Dispatch API. ## Project ```json { "id": "uuid", "user_id": "uuid", "name": "My Project", "slug": "my-project", "header_prefix": "dispatch", "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z", "archived_at": null } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique project identifier | | `user_id` | UUID | Owning user | | `name` | string | Display name | | `slug` | string | URL-safe identifier | | `header_prefix` | string | Prefix for outgoing `X-{prefix}-*` headers | | `archived_at` | timestamp | Soft delete timestamp (null if active) | ## WebhookEndpoint ```json { "id": "uuid", "project_id": "uuid", "name": "GitHub Webhooks", "slug": "github-webhooks", "signing_secret": null, "event_type_path": "action", "is_active": true, "provider": "github", "provider_repo": "owner/repo", "provider_webhook_id": "123456", "oauth_connection_id": "uuid", "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z" } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique endpoint identifier | | `project_id` | UUID | Parent project | | `name` | string | Display name | | `slug` | string | URL slug for `POST /hooks/:slug` | | `signing_secret` | string | Verification secret (hidden in responses) | | `event_type_path` | string | Dot-notation path to extract event type from body | | `is_active` | boolean | Whether the endpoint accepts requests | | `provider` | string | `"github"`, `"gitlab"`, `"bitbucket"`, `"slack"`, `"jira"`, `"linear"`, `"stripe"`, or null | | `provider_repo` | string | Repository/project identifier (provider-specific) | | `provider_webhook_id` | string | External webhook ID registered on the provider | | `oauth_connection_id` | UUID | Linked OAuth connection | ## Destination ```json { "id": "uuid", "project_id": "uuid", "name": "Discord #alerts", "type": "discord", "guild_id": "123456789", "channel_id": "987654321", "retry_policy": { "max_attempts": 7, "backoff": [1, 5, 30, 120, 900, 3600, 21600] }, "event_types": ["push", "pull_request"], "is_active": true, "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z" } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique destination identifier | | `project_id` | UUID | Parent project | | `name` | string | Display name | | `type` | string | `"discord"`, `"slack"`, `"telegram"`, or `"webhook"` | | `guild_id` | string | Discord server ID | | `channel_id` | string | Discord/Slack channel ID, or Telegram chat ID (e.g., `@mychannel` or `-1001234567890`) | | `retry_policy` | object | Retry configuration | | `event_types` | string[] | Event type allowlist (empty = all) | | `is_active` | boolean | Whether delivery is enabled | ## EndpointDestination (Link) ```json { "endpoint_id": "uuid", "destination_id": "uuid", "template_id": "uuid", "transform_id": "uuid", "routing_rules": [], "header_prefix": "dispatch" } ``` | Field | Type | Description | |-------|------|-------------| | `endpoint_id` | UUID | Linked endpoint | | `destination_id` | UUID | Linked destination | | `template_id` | UUID | Default message template (optional) | | `transform_id` | UUID | Named Transform to apply before formatting; unset to disable (optional) | | `routing_rules` | array | Conditional template/transform rules | | `header_prefix` | string | From project config | ## MessageTemplate Templates support two config versions. The `config` field stores either a v1 flat object or a v2 multi-platform object. **v1 (flat):** ```json { "id": "uuid", "project_id": "uuid", "name": "GitHub Push Template", "config": { "title": "{{.Meta.EventType}}", "description": "{{.Body.repository.full_name}}", "color": 15105570, "fields": [], "footer": "Dispatch", "timestamp": true }, "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z" } ``` **v2 (multi-platform):** ```json { "id": "uuid", "project_id": "uuid", "name": "Multi-Platform Template", "config": { "version": 2, "platforms": { "discord": { "title": "{{.Meta.EventType}}", "description": "{{.Body.repository.full_name}}", "color": 5763719, "timestamp": true }, "slack": { "slack_blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*{{.Meta.EventType}}*" } } ] }, "telegram": { "telegram_html": "{{.Meta.EventType}}\n{{.Body.repository.full_name}}" }, "default": { "title": "{{.Meta.EventType}}", "timestamp": true } } }, "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z" } ``` See [Message Templates](/concepts/templates) for the full template config reference. ## Transform ```json { "id": "uuid", "project_id": "uuid", "name": "Extract PR fields", "expression": "{ \"action\": action, \"title\": pull_request.title, \"url\": pull_request.html_url }", "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z" } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique transform identifier | | `project_id` | UUID | Parent project | | `name` | string | Display name | | `expression` | string | JSONata expression | ## Event ```json { "id": "uuid", "endpoint_id": "uuid", "organization_id": "uuid", "event_type": "push", "payload": {}, "headers": {}, "source_ip": "192.168.1.1", "idempotency_key": null, "status": "received", "created_at": "2025-03-14T10:00:00Z" } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique event identifier | | `endpoint_id` | UUID | Receiving endpoint | | `organization_id` | UUID | Owning organization | | `event_type` | string | Extracted event type | | `payload` | object | Full JSON request body | | `headers` | object | All request headers | | `source_ip` | string | Sender IP address | | `idempotency_key` | string | Deduplication key (derived from request or `X-Idempotency-Key` header) | | `status` | string | `"received"` or `"filtered"` | ## DeliveryAttempt ```json { "id": "uuid", "event_id": "uuid", "destination_id": "uuid", "attempt_number": 1, "status": "success", "status_code": 200, "response_body": "{\"ok\": true}", "response_headers": {}, "error": null, "latency_ms": 245, "next_retry_at": null, "payload_sent": {}, "created_at": "2025-03-14T10:00:00Z" } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique attempt identifier | | `event_id` | UUID | Source event | | `destination_id` | UUID | Target destination | | `attempt_number` | integer | 1-based attempt counter | | `status` | string | `"queued"`, `"success"`, `"failed"`, `"retrying"`, `"canceled"` | | `status_code` | integer | HTTP response status code | | `response_body` | string | First 4KB of response body | | `response_headers` | object | Response headers | | `error` | string | Error message | | `latency_ms` | integer | Round-trip time in milliseconds | | `next_retry_at` | timestamp | Scheduled retry time | | `payload_sent` | object | Actual payload delivered to the destination | ## FilterGroup ```json { "id": "uuid", "endpoint_id": "uuid", "name": "Accept pushes to main", "logic": "AND", "is_enabled": true, "conditions": [] } ``` ## FilterCondition ```json { "id": "uuid", "filter_id": "uuid", "field": "body.ref", "operator": "eq", "value": "refs/heads/main", "position": 0 } ``` ## APIKey ```json { "id": "uuid", "project_id": "uuid", "name": "CI/CD Key", "prefix": "dsp_abc", "created_at": "2025-03-14T10:00:00Z", "revoked_at": null } ``` | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Unique key identifier | | `project_id` | UUID | Parent project | | `name` | string | Display name | | `prefix` | string | Visible key prefix (e.g., `dsp_abc`) | | `revoked_at` | timestamp | Revocation time (null if active) | ## OAuthConnection ```json { "id": "uuid", "project_id": "uuid", "provider": "github", "provider_user_id": "12345", "provider_username": "octocat", "scopes": "repo,admin:repo_hook", "metadata": {}, "created_at": "2025-03-14T10:00:00Z", "updated_at": "2025-03-14T10:00:00Z" } ``` | Field | Type | Description | |-------|------|-------------| | `provider` | string | `"github"`, `"gitlab"`, `"bitbucket"`, `"slack"`, `"jira"`, `"linear"`, `"stripe"`, or `"discord"` | | `provider_user_id` | string | User ID on the provider | | `provider_username` | string | Username on the provider | | `scopes` | string | Granted OAuth scopes | | `metadata` | object | Additional provider-specific data | ## Entity Relationships ``` Organization ├── OrganizationMember (roles: owner, admin, developer, viewer) └── Project ├── WebhookEndpoint │ ├── FilterGroup → FilterCondition │ └── EndpointDestination (link) ──┐ ├── Destination ◄──────────────────────┘ ├── MessageTemplate ├── Transform ├── APIKey ├── OAuthConnection └── Event → DeliveryAttempt ``` --- # Integrations Dispatch integrates with the following services. ## Sources Receive webhooks from these services into Dispatch: | Integration | Description | |-------------|-------------| | [GitHub](/integrations/github) | Repositories, pull requests, issues, Actions | | [GitLab](/integrations/gitlab) | Projects, merge requests, issues, pipelines | | [Bitbucket](/integrations/bitbucket) | Repositories, pull requests | | [Slack](/integrations/slack) | Event callbacks from Slack apps | | [Jira](/integrations/jira) | Issue created, updated, deleted | | [Linear](/integrations/linear) | Issues, comments, projects | | [Stripe](/integrations/stripe) | Charges, payments, subscriptions, invoices | | [PayPal](/integrations/paypal) | Payments, subscriptions, disputes, invoices | | [Sentry](/integrations/sentry) | Issues, errors, metric alerts, and AI autofix events | | [Salesforce](/integrations/salesforce) | Account, Contact, Lead, Opportunity, Case, and any SObject record events | | [Generic Webhook](/integrations/webhook) | Any service that supports outgoing HTTP webhooks | ## Destinations Deliver formatted messages to these services from Dispatch: | Integration | Description | |-------------|-------------| | [Discord](/integrations/discord) | Embeds delivered via webhook or OAuth | | [Slack](/integrations/slack) | Messages delivered via OAuth bot | | [Telegram](/integrations/telegram) | Messages delivered via Telegram bot | | [Generic Webhook](/integrations/webhook) | Any HTTP endpoint | --- # GitHub Integration Dispatch provides a first-class GitHub integration that automatically creates webhook endpoints for your repositories. ## Connecting GitHub 1. Navigate to your project's **Sources** page 2. Click **Connect GitHub** 3. Authorize the Dispatch GitHub app 4. Select a repository 5. Choose which events to receive (see [Event Selection](#event-selection) below) Dispatch will: - Create a webhook endpoint with `provider = "github"` - Register a webhook on the selected GitHub repository pointing to your Dispatch endpoint - Configure the endpoint to receive the selected event types ## Event Selection When connecting a GitHub repository, choose one of three modes: | Mode | Description | |------|-------------| | **Push only** | Receive only `push` events (commits). Lightweight option for CI/CD notifications. | | **All events** | Receive every event type GitHub can send for the repository. | | **Custom** | Pick specific event types from a grouped list. Organized by category: Code, Pull Requests, Issues, Discussions, Releases, Actions, and more. | You can change the event selection at any time by disconnecting and reconnecting the repository, or by editing the webhook in GitHub directly. ## Supported GitHub Events GitHub sends events via the `X-GitHub-Event` header. Dispatch auto-formats the following event types into rich Discord embeds: | Event | Description | |-------|-------------| | `push` | Commits pushed to a branch | | `pull_request` | PR opened, closed, merged, labeled, etc. | | `issues` | Issue opened, closed, assigned, labeled, etc. | | `issue_comment` | Comment on an issue or PR | | `release` | Release published, created, edited | | `create` | Branch or tag created | | `delete` | Branch or tag deleted | | `fork` | Repository forked | | `star` | Repository starred | | `workflow_run` | GitHub Actions workflow completed | | `workflow_job` | GitHub Actions job status change | | `check_run` | Check run completed | | `check_suite` | Check suite completed | | `deployment` | Deployment created | | `deployment_status` | Deployment status updated | | `ping` | Webhook connectivity test | ## Signature Verification GitHub webhooks use the `X-Hub-Signature-256` header with HMAC-SHA256. Dispatch automatically verifies this signature using the endpoint's signing secret, which is set as the webhook secret on the GitHub side. ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects GitHub payloads (by the presence of `repository` and `sender` fields) and generates specialized embeds: ### Push Events - Shows branch, commit count, and commit messages - Links to the compare view - Color-coded by branch (green for main/master) ### Pull Request Events - Shows PR title, description, status, and labels - Includes reviewer information - Color-coded by action (green for opened, purple for merged, red for closed) ### Issue Events - Shows issue title, body, assignees, and labels - Color-coded by action ### Release Events - Shows release name, tag, and description - Links to the release page ## Disconnecting To disconnect a GitHub repository: 1. Go to **Sources** and find the GitHub endpoint 2. Click **Disconnect** 3. This removes the webhook from GitHub and deactivates the endpoint You can also disconnect all GitHub connections from the project integration settings. --- # GitLab Integration Dispatch provides a first-class GitLab integration that automatically creates webhook endpoints for your projects. ## Connecting GitLab 1. Navigate to your project's **Sources** page 2. Click **Connect GitLab** 3. Authorize the Dispatch GitLab OAuth app 4. Select a GitLab project 5. Choose which events to receive (see [Event Selection](#event-selection) below) Dispatch will: - Create a webhook endpoint with `provider = "gitlab"` - Register a webhook on the selected GitLab project pointing to your Dispatch endpoint - Configure the endpoint to receive the selected event types ## Event Selection When connecting a GitLab project, choose one of three modes: | Mode | Description | |------|-------------| | **Push only** | Receive only push (commit) events. | | **All events** | Receive every event type GitLab supports for the project. | | **Custom** | Pick specific event types from the full list of supported GitLab hooks. | ## Supported GitLab Events GitLab sends the event type in the `X-Gitlab-Event` header. Dispatch auto-formats the following event types: | Event | Description | |-------|-------------| | `Push Hook` | Commits pushed to a branch | | `Merge Request Hook` | MR opened, closed, merged, approved | | `Issue Hook` | Issue opened, closed, updated | | `Note Hook` | Comment on an issue, MR, commit, or snippet | | `Tag Push Hook` | Tag created or deleted | | `Pipeline Hook` | Pipeline created or status changed | | `Job Hook` | Job status change | | `Release Hook` | Release created or updated | | `Deployment Hook` | Deployment created or updated | | `Member Hook` | Project member added or removed | | `Confidential Issue Hook` | Confidential issue events | ## Signature Verification GitLab webhooks include an `X-Gitlab-Token` header containing a secret token. Dispatch performs a **literal match** of this header against the endpoint's signing secret. When you connect GitLab via OAuth, Dispatch automatically sets the signing secret on the GitLab webhook. If configuring manually, set the same value in both: - The GitLab webhook **Secret Token** field - The Dispatch endpoint's **Signing Secret** ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects GitLab payloads and generates specialized embeds: ### Push Hook - Shows branch name, commit count, and commit messages - Links to the compare view - Color-coded by branch ### Merge Request Hook - Shows MR title, description, and status - Includes source/target branch info - Color-coded by action (green for opened, purple for merged, red for closed) ### Issue Hook - Shows issue title, description, and labels - Includes assignee information - Color-coded by action ## Disconnecting To disconnect a GitLab project: 1. Go to **Sources** and find the GitLab endpoint 2. Click **Disconnect** 3. Dispatch removes the webhook from GitLab and deactivates the endpoint --- # Bitbucket Integration Dispatch integrates with Bitbucket to automatically receive webhook events from your repositories. ## Connecting Bitbucket 1. Navigate to your project's **Sources** page 2. Click **Connect Bitbucket** 3. Authorize the Dispatch Bitbucket OAuth app 4. Select a repository 5. Choose which events to receive (see [Event Selection](#event-selection) below) Dispatch will: - Create a webhook endpoint with `provider = "bitbucket"` - Register a webhook on the selected Bitbucket repository - Configure the endpoint to receive the selected event types ## Event Selection When connecting a Bitbucket repository, choose one of three modes: | Mode | Description | |------|-------------| | **Push only** | Receive only `repo:push` events. | | **All events** | Receive every event type Bitbucket supports for the repository. | | **Custom** | Pick specific event types from the full list of supported Bitbucket triggers. | ## Supported Bitbucket Events | Event | Description | |-------|-------------| | `repo:push` | Commits pushed to any branch | | `pullrequest:created` | Pull request opened | | `pullrequest:updated` | Pull request updated | | `pullrequest:approved` | Pull request approved | | `pullrequest:unapproved` | Pull request approval removed | | `pullrequest:fulfilled` | Pull request merged | | `pullrequest:rejected` | Pull request declined | | `pullrequest:comment_created` | Comment added to pull request | | `issue:created` | Issue created | | `issue:updated` | Issue updated | | `issue:comment_created` | Comment added to issue | ## Signature Verification Bitbucket webhooks include an `X-Hub-Signature` header with an HMAC-SHA256 signature of the request body. The format matches GitHub-style verification: ``` X-Hub-Signature: sha256= ``` Dispatch automatically verifies this against the endpoint's signing secret. ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects Bitbucket payloads and generates specialized embeds: ### repo:push - Shows repository name, branch, and commit summary - Lists commit messages with authors - Links to the diff view ### Pull Request Events - Shows PR title, description, and status - Includes author and reviewer information - Color-coded by action (green for created, purple for fulfilled/merged, red for rejected) ## Disconnecting To disconnect a Bitbucket repository: 1. Go to **Sources** and find the Bitbucket endpoint 2. Click **Disconnect** 3. Dispatch removes the webhook from Bitbucket and deactivates the endpoint --- # Slack Integration Dispatch integrates with Slack as both a **source** (receive Slack events) and a **destination** (post messages to Slack channels). ## Slack as a Source ### Connecting 1. Navigate to your project's **Sources** page 2. Click **Connect Slack** 3. Authorize the Dispatch Slack app for your workspace 4. Select a channel to receive events from Dispatch will: - Create a webhook endpoint with `provider = "slack"` - Subscribe to events from the selected channel ### Slack URL Verification When Slack sends a URL verification challenge, Dispatch handles it automatically: ```json // Slack sends: { "type": "url_verification", "challenge": "abc123" } // Dispatch responds: { "challenge": "abc123" } ``` No event is created for verification requests. ### Signature Verification Slack webhooks use the `X-Slack-Signature` header with the format: ``` v0=HMAC-SHA256(signing_secret, "v0:{timestamp}:{body}") ``` The `X-Slack-Request-Timestamp` header must be within 5 minutes of the current time. Dispatch verifies this automatically. ### Supported Slack Events Dispatch auto-formats the following Slack event types: | Event | Description | |-------|-------------| | `message` | Message posted in a channel | | `reaction_added` / `reaction_removed` | Emoji reaction changes | | `channel_created` / `channel_archive` / `channel_unarchive` | Channel lifecycle | | `member_joined_channel` / `member_left_channel` | Channel membership changes | | `pin_added` / `pin_removed` | Pinned message changes | | `file_shared` / `file_created` | File uploads | | `emoji_changed` | Custom emoji additions/removals | | `team_join` | New member joined workspace | | `app_mention` | Bot mentioned in a channel | ## Slack as a Destination ### Connecting 1. Navigate to your project's **Destinations** page 2. Click **Connect Slack** 3. Authorize the Dispatch Slack app 4. Select a channel to post messages to Dispatch will: - Create a destination with `type = "slack"` - Store the bot token (encrypted at rest) - Use the Slack API to post messages to the selected channel ### Message Format When delivering to Slack, Dispatch formats messages as Slack attachments: ```json { "text": "New event received", "username": "Dispatch", "attachments": [ { "color": "#e67e22", "title": "push", "text": "3 commits pushed to main", "fields": [ { "title": "Repository", "value": "owner/repo", "short": true } ], "footer": "Dispatch", "ts": 1710432000 } ] } ``` ### Disconnecting To disconnect Slack: 1. Go to **Sources** or **Destinations** and find the Slack connection 2. Click **Disconnect** 3. This revokes the OAuth token and deactivates the endpoint/destination --- # Discord Integration Discord is the primary destination type in Dispatch. Events are delivered as rich embed messages to Discord channels. ## Setting Up a Discord Destination ### Option 1: Manual Webhook URL 1. In Discord, go to **Server Settings** → **Integrations** → **Webhooks** 2. Create a new webhook and copy the URL 3. In Dispatch, go to **Destinations** → **Create Destination** 4. Select **Discord** as the type and paste the webhook URL ### Option 2: OAuth Connection 1. Go to **Destinations** → **Connect Discord** 2. Authorize the Dispatch bot for your server 3. Select a channel 4. Dispatch creates the webhook automatically ## Discord Embed Format Dispatch delivers events as Discord webhook messages with rich embeds: ```json { "content": "", "username": "Dispatch", "avatar_url": "", "embeds": [ { "title": "push", "description": "3 commits pushed to main", "color": 15105570, "fields": [ { "name": "Repository", "value": "owner/repo", "inline": true }, { "name": "Branch", "value": "main", "inline": true } ], "footer": { "text": "Dispatch" }, "timestamp": "2025-03-14T10:30:00Z" } ] } ``` ### Embed Limits Discord enforces these limits on embed content: | Field | Max Length | |-------|-----------| | Title | 256 characters | | Description | 4096 characters | | Fields | 25 per embed | | Field name | 256 characters | | Field value | 1024 characters | | Footer text | 2048 characters | | Author name | 256 characters | ### Colors Embed colors are specified as decimal integers. Common values: | Color | Decimal | Hex | |-------|---------|-----| | Red | 16711680 | `#FF0000` | | Green | 65280 | `#00FF00` | | Blue | 255 | `#0000FF` | | Orange (Dispatch) | 15105570 | `#E67E22` | | Purple | 10181046 | `#9B59B6` | | Yellow | 16776960 | `#FFFF00` | ## ANSI Formatting Discord supports ANSI color codes in code blocks. Dispatch templates provide helper functions for ANSI formatting: ``` {{ansiGreen "SUCCESS"}} → Green text {{ansiRed "ERROR"}} → Red text {{ansiYellow "WARNING"}} → Yellow text {{ansiBold "Important"}} → Bold text ``` These are rendered inside Discord's ANSI code block syntax (`ansi` language identifier). ## Rate Limiting Discord enforces rate limits on webhook endpoints. Dispatch handles this automatically: - **Cooldown** — per-destination cooldown period tracked across all delivery workers - **Retry-After** — respects Discord's `Retry-After` header on 429 responses If Discord returns a 429, the delivery is automatically retried after the specified cooldown period. ## Security - Webhook URLs are encrypted at rest - URLs are only decrypted at delivery time and are never exposed in API responses or logs --- # Telegram Telegram is a **destination** in Dispatch — events are delivered to Telegram direct messages, groups, or channels via the Dispatch bot. > **Note:** Telegram is not a webhook source. You cannot receive webhooks *from* Telegram into Dispatch. It is only used as a delivery target. ## Connecting a Telegram Destination When adding a Telegram destination, choose one of two modes: --- ### Direct Messages Send notifications to your personal Telegram DMs. 1. Click **Connect** on the Telegram card and select **Direct messages** 2. Click **Log in with Telegram** — you will be redirected to authenticate with your Telegram account 3. After authenticating, open a conversation with the bot in Telegram and press **Start** (this is required by Telegram before a bot can DM you) 4. Return to the dashboard and click **Verify DMs are open** 5. Once verified, name the destination and click **Connect** --- ### Group or Channel Send notifications to a Telegram group, supergroup, or channel. 1. Click **Connect** on the Telegram card and select **Group or channel** 2. Add the Dispatch bot to your group or channel - For **channels**: make the bot an administrator with permission to post messages 3. Click **Generate verification code** in the dashboard 4. A unique code is displayed (e.g., `/ABCDEF12`). Send this command in the group or channel — any member can send it 5. Dispatch automatically detects the group once the code is received 6. Name the destination and click **Connect** The verification code expires after 3 minutes. If it times out, generate a new one. #### Manual Chat ID Entry If you prefer to skip code-based discovery, expand **Enter chat ID manually** and paste either: - A numeric chat ID (e.g., `-1001234567890`) - A public channel username (e.g., `@mychannel`) - A `t.me` message link (e.g., `https://t.me/c/1234567890/1`) Click **Verify** to confirm the bot has access, then connect. --- ## Message Formatting By default, Dispatch converts the embed format into Telegram HTML. For full control, use a [template](/concepts/templates) with a `telegram_html` field. ### Auto-Converted Format When no template is assigned, Dispatch auto-converts the embed to Telegram HTML: - Title becomes bold text - Description is included as-is - Fields are formatted as `name: value` pairs - Footer is appended at the end ### Native Telegram HTML Use a v2 multi-platform template with a `telegram` platform variant for full control: ```json { "version": 2, "platforms": { "telegram": { "telegram_html": "{{upper .Meta.EventType}}\n\nRepo: {{.Body.repository.full_name}}\nAction: {{.Body.action}}\n\nOpened by {{.Body.sender.login}}" } } } ``` **Supported HTML tags:** | Tag | Renders as | |-----|------------| | `` / `` | Bold | | `` / `` | Italic | | `` / `` | Underline | | `` / `` / `` | Strikethrough | | `` | Inline code | | `
` | Code block |
| `
` | Block quote | | `` | Hyperlink | | `` | Hidden spoiler | ## Rate Limiting Telegram enforces rate limits per bot and per chat. Dispatch automatically handles rate limit responses by rescheduling the delivery rather than counting it as a failure. --- # Jira Integration Dispatch integrates with Jira to receive webhook events when issues are created, updated, or deleted in your projects. ## Connecting Jira 1. Navigate to your project's **Sources** page 2. Click **Connect Jira** 3. Authorize the Dispatch Jira OAuth app 4. Select a Jira project and the events you want to receive Dispatch will: - Create a webhook endpoint with `provider = "jira"` - Register a webhook on the selected Jira project - Configure the endpoint to receive the selected event types ## Supported Jira Events | Event | Description | |-------|-------------| | `jira:issue_created` | Issue created | | `jira:issue_updated` | Issue field changed | | `jira:issue_deleted` | Issue deleted | | `comment_created` | Comment added to an issue | | `comment_updated` | Comment edited | | `comment_deleted` | Comment removed | The event type is extracted from the `webhookEvent` field in the Jira payload. ## Signature Verification Jira webhooks include a JWT in the `Authorization` header. Dispatch verifies this automatically. When connecting via OAuth, Dispatch manages the signing secret configuration for you. ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects Jira payloads and generates specialized embeds: ### jira:issue_created - Shows issue key and summary - Includes issue type, priority, and assignee - Links to the issue in Jira ### jira:issue_updated - Shows the issue key and what changed - Displays old and new field values for changed fields ### jira:issue_deleted - Shows the issue key and summary of the deleted issue ## Disconnecting To disconnect a Jira project: 1. Go to **Sources** and find the Jira endpoint 2. Click **Disconnect** 3. Dispatch removes the webhook from Jira and deactivates the endpoint --- # Linear Integration Dispatch integrates with Linear to receive webhook events when issues, comments, and projects change in your workspace. ## Connecting Linear 1. Navigate to your project's **Sources** page 2. Click **Connect Linear** 3. Authorize the Dispatch Linear OAuth app 4. Select the events you want to receive Dispatch will: - Create a webhook endpoint with `provider = "linear"` - Register a webhook on your Linear workspace pointing to your Dispatch endpoint - Configure the endpoint to receive the selected event types ## Supported Linear Events The event type is extracted from the `type` field in the Linear payload. | Event Type | Description | |------------|-------------| | `Issue` | Issue created, updated, or removed | | `Comment` | Comment created, updated, or removed | | `IssueLabel` | Label created or updated | | `Project` | Project created or updated | | `ProjectUpdate` | Project update posted | | `Cycle` | Cycle started, completed, or updated | | `Team` | Team settings changed | | `User` | User added or updated | ## Signature Verification Linear webhooks include a `Linear-Signature` header containing an HMAC-SHA256 hex digest of the request body. Dispatch verifies this against the endpoint's signing secret. ``` Linear-Signature: ``` When connecting via OAuth, Dispatch automatically sets and stores the signing secret. ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects Linear payloads and generates specialized embeds: ### Issue Events - Shows issue title, status, priority, and assignee - Includes team and project context - Color-coded by priority (red for urgent, orange for high, blue for medium, gray for low) - Shows what changed for update events ### Comment Events - Shows the comment text and the issue it was added to - Includes the comment author ### Project Updates - Shows the project name and update content ## Disconnecting To disconnect a Linear workspace: 1. Go to **Sources** and find the Linear endpoint 2. Click **Disconnect** 3. Dispatch removes the webhook from Linear and deactivates the endpoint --- # Stripe Integration Dispatch integrates with Stripe to receive webhook events for charges, payments, subscriptions, and more. ## Connecting Stripe 1. Navigate to your project's **Sources** page 2. Click **Connect Stripe** 3. Authorize with your Stripe account 4. Select the Stripe events you want to receive Dispatch will: - Create a webhook endpoint with `provider = "stripe"` - Register a webhook endpoint in your Stripe Dashboard - Set the signing secret to your Stripe webhook's signing secret ### Manual Setup If you prefer to configure the endpoint manually: 1. Create a webhook endpoint in your [Stripe Dashboard](https://dashboard.stripe.com/webhooks) 2. Set the endpoint URL to your Dispatch endpoint URL 3. Copy the **Signing secret** (starts with `whsec_`) from the Stripe webhook page 4. Create a Dispatch source and paste the signing secret into the **Signing Secret** field ## Supported Stripe Events The event type is extracted from the `type` field in the Stripe payload (e.g., `payment_intent.succeeded`). | Category | Events | |----------|--------| | **Charges** | `charge.succeeded`, `charge.failed`, `charge.refunded` | | **Payment Intents** | `payment_intent.succeeded`, `payment_intent.payment_failed`, `payment_intent.created`, `payment_intent.canceled` | | **Subscriptions** | `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted` | | **Invoices** | `invoice.paid`, `invoice.payment_failed`, `invoice.finalized` | | **Customers** | `customer.created`, `customer.updated`, `customer.deleted` | | **Checkout** | `checkout.session.completed`, `checkout.session.expired` | Stripe can send any event type — configure your Stripe webhook to send only the events you need. ## Signature Verification Stripe webhooks include a `Stripe-Signature` header. Dispatch verifies this automatically using your webhook's signing secret. ``` Stripe-Signature: t=1614556800,v1=... ``` Use the signing secret exactly as shown in your Stripe Dashboard — it begins with `whsec_`. ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects Stripe payloads and generates specialized embeds: ### Charge Events - Shows amount, currency, and status - Includes customer email and payment method details ### Payment Intent Events - Shows amount, currency, and intent status - Includes failure reason for failed payments ### Subscription Events - Shows plan name, interval, and customer - Color-coded by action (green for created, blue for updated, red for canceled) ### Invoice Events - Shows amount due, due date, and customer - Includes line item count ## Disconnecting To disconnect Stripe: 1. Go to **Sources** and find the Stripe endpoint 2. Click **Disconnect** 3. Dispatch deactivates the endpoint; you should also delete the webhook endpoint from your Stripe Dashboard --- # PayPal Receive PayPal webhook notifications for payments, subscriptions, disputes, and invoices. ## How It Works PayPal uses its own signature verification scheme. Dispatch verifies each incoming request using PayPal's transmission headers and your registered Webhook ID, ensuring only genuine PayPal events are processed. ## Setup ### 1. Create a Source in Dispatch 1. Go to **Sources** in your project 2. Click **Add Source** and select **PayPal** 3. Click **Generate webhook URL** to create the endpoint 4. Copy the generated webhook URL ### 2. Register in PayPal Developer Dashboard 1. Open the [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/) 2. Navigate to **Apps & Credentials** and select your app 3. Under **Webhooks**, click **Add Webhook** 4. Paste the Dispatch webhook URL 5. Select the event types you want to receive 6. Save the webhook — PayPal will display a **Webhook ID** 7. Copy the Webhook ID ### 3. Complete Setup in Dispatch 1. Return to the Dispatch source setup 2. Enter the **Webhook ID** from PayPal 3. Click **Done** ## Signature Verification PayPal signs each request with the following headers: | Header | Description | |--------|-------------| | `PAYPAL-TRANSMISSION-ID` | Unique transmission identifier | | `PAYPAL-TRANSMISSION-TIME` | Timestamp of the transmission | | `PAYPAL-TRANSMISSION-SIG` | HMAC-SHA256 signature | | `PAYPAL-CERT-URL` | URL to the PayPal certificate used for signing | | `PAYPAL-AUTH-ALGO` | Algorithm used (e.g., `SHA256withRSA`) | Dispatch verifies the signature using your Webhook ID and PayPal's certificate before accepting the event. ## Supported Event Types | Event Type | Description | |------------|-------------| | `PAYMENT.CAPTURE.COMPLETED` | Payment capture succeeded | | `PAYMENT.CAPTURE.DENIED` | Payment capture was denied | | `PAYMENT.CAPTURE.REFUNDED` | Payment capture was refunded | | `PAYMENT.SALE.COMPLETED` | Sale transaction completed | | `PAYMENT.SALE.REFUNDED` | Sale transaction was refunded | | `INVOICING.INVOICE.PAID` | Invoice was paid | | `INVOICING.INVOICE.CANCELLED` | Invoice was cancelled | | `BILLING.SUBSCRIPTION.CREATED` | Subscription created | | `BILLING.SUBSCRIPTION.ACTIVATED` | Subscription activated | | `BILLING.SUBSCRIPTION.CANCELLED` | Subscription cancelled | | `BILLING.SUBSCRIPTION.SUSPENDED` | Subscription suspended | | `BILLING.SUBSCRIPTION.PAYMENT.FAILED` | Subscription payment failed | | `CUSTOMER.DISPUTE.CREATED` | Dispute opened by customer | | `CUSTOMER.DISPUTE.RESOLVED` | Dispute resolved | | `RISK.DISPUTE.CREATED` | Dispute created by risk team | ## Disconnecting To remove the PayPal integration from a source: 1. Delete or disable the webhook in the PayPal Developer Dashboard 2. Delete the source in Dispatch, or click **Disconnect** on the source --- # Sentry Integration Dispatch integrates with Sentry to receive webhook events for issues, errors, alerts, and AI autofix (Seer) activity. ## Connecting Sentry Sentry webhooks are sent from a custom Sentry integration (internal app). To set up: 1. In your Sentry organization, go to **Settings → Developer Settings → New Internal Integration** 2. Under **Webhooks**, enable the resource types you want (Issues, Errors, Comments, etc.) 3. Set the **Webhook URL** to your Dispatch endpoint URL 4. Copy the **Client Secret** shown on the integration page — this is your signing secret 5. Create a Dispatch source with `provider = sentry` and paste the signing secret into the **Signing Secret** field Dispatch auto-detects Sentry payloads by the presence of the `sentry-hook-resource` header, so no manual provider selection is required. ## Supported Sentry Events The event type is extracted from the `sentry-hook-resource` header combined with the `action` field in the payload (e.g., `issue.created`, `metric_alert.critical`). | Resource | Actions | |----------|---------| | **Issues** | `created`, `resolved`, `assigned`, `ignored`, `archived`, `unresolved`, `deleted` | | **Errors** | `created` | | **Metric Alerts** | `critical`, `warning`, `resolved` | | **Issue Alerts** | `triggered` | | **Comments** | `created`, `updated`, `deleted` | | **Installation** | `created`, `deleted` | | **Seer (AI Autofix)** | `root_cause_started`, `root_cause_completed`, `solution_started`, `solution_completed`, `coding_started`, `coding_completed`, `pr_created` | ## Signature Verification Sentry signs webhook payloads with HMAC-SHA256. The signature is sent in the `sentry-hook-signature` header. Dispatch verifies this automatically when a signing secret is configured on the endpoint. ``` sentry-hook-signature: sentry-hook-resource: issue ``` If no signing secret is configured the signature check is skipped — useful for local development, but not recommended for production. ## Auto-Formatted Embeds When no custom template is assigned, Dispatch auto-detects Sentry payloads and generates specialized embeds: ### Issue Events - Shows the short ID, error title, culprit location, level, status, and priority - First seen / last seen timestamps - Color-coded by severity: red (error/fatal), orange (warning), blue (info), gray (debug/archived), green (resolved) ### Error Events - Shows exception type and value, culprit location, platform, and linked issue - Color-coded by severity level ### Metric Alert Events - Shows alert rule name, trigger description, affected projects, metric aggregate, and time window - Color-coded: red (critical), orange (warning), green (resolved) ### Issue Alert (Event Alert) Events - Shows the alert rule name that fired, the triggering error title and culprit, and the linked issue - Orange color to indicate an active alert ### Comment Events - Shows the comment text (truncated), author, and linked issue ID - Blue for created/updated, gray for deleted ### Seer AI Autofix Events - Tracks each stage of the Seer autofix pipeline - For completed stages, shows the root cause description, solution summary, or code changes with branch names and PR links ## Disconnecting To stop receiving Sentry webhooks: 1. In your Sentry integration settings, remove or disable the webhook URL 2. In Dispatch, deactivate or delete the endpoint --- # Salesforce Integration Dispatch receives Salesforce record events from an Apex trigger or Flow HTTP callout in your org. Salesforce has no native outbound webhook product, so the customer-side trigger pushes JSON to your Dispatch source URL. ## Connecting Salesforce 1. Open your project's **Sources** page 2. Click **Add Source → Salesforce** 3. Authorize the Connected App in your Salesforce org 4. Click **Generate webhook URL** — Dispatch shows you the URL and a freshly generated signing secret 5. Copy both into your Apex trigger / Flow HTTP callout Sandboxes: log in via `https://test.salesforce.com` instead of the production login URL. ## Sending events from Apex The receiver accepts a small, opinionated JSON shape. The event type comes from the `X-Salesforce-Event` header (or from `event_type` in the body), and the record fields go under `record`: ```json { "event_type": "Opportunity.updated", "instance_url": "https://example.my.salesforce.com", "record": { "Id": "0061x00000D4eF5AAD", "Name": "Acme - Enterprise Renewal", "StageName": "Closed Won", "Amount": 135000, "IsClosed": true, "IsWon": true }, "old": { "StageName": "Negotiation/Review" } } ``` Required headers: ``` Content-Type: application/json X-Salesforce-Event: Opportunity.updated X-Salesforce-Signature: sha256= ``` The `old` object is optional but lets Dispatch render stage-transition embeds. ## Supported events The event picker covers every standard CRM and Service Cloud object. You are not limited to this list — any `.` string is accepted, and custom objects (`MyObject__c.created`) are auto-detected. | Category | Examples | |----------|----------| | **CRM core** | `Account.created`, `Contact.updated`, `Lead.converted` | | **Sales pipeline** | `Opportunity.created`, `Opportunity.updated`, `Quote.accepted`, `Order.activated`, `Contract.activated` | | **Catalogue** | `Product2.created`, `PricebookEntry.updated` | | **Marketing** | `Campaign.created`, `CampaignMember.created` | | **Service Cloud** | `Case.created`, `Case.escalated`, `CaseComment.created` | | **Activities** | `Task.completed`, `Event.created` | | **Platform** | `User.created`, `Asset.created` | | **Custom objects** | `*__c.created`, `*__c.updated`, `*__c.deleted` | ## Signature verification `X-Salesforce-Signature` is an HMAC-SHA256 hex digest of the raw request body keyed with the source signing secret. The `sha256=` prefix is optional. When the source is created without a signing secret, signature verification is skipped (handy for testing from Postman). ## Auto-formatted embeds When no template is assigned, Dispatch generates rich Discord embeds tailored to the SObject: - **Account** — name, type, industry, annual revenue, owner - **Contact** — name, title, email, phone, account - **Lead** — name, company, status, source, email - **Opportunity** — stage transition (`Old → New`), amount, probability, close date, account; colour-coded green for won, red for lost - **Case** — number, subject, status, priority, origin; colour-coded by priority - **Quote / Order / Contract** — totals, status, dates - **Campaign / Product / Activity / User / Asset** — relevant headline fields - **Custom objects** — name + action with the raw record fields available to templates If `instance_url` and the record `Id` are present, the embed title links straight to the Lightning record page. ## Disconnecting 1. Open **Sources** and find the Salesforce endpoint 2. Click **Disconnect** Dispatch deactivates the endpoint. Remove or disable the matching Apex trigger / Flow in Salesforce so it stops sending callouts. --- # Generic Webhooks Dispatch works with any service that can send or receive HTTP webhooks — no specific integration required. ## Receiving Webhooks (Source) Any service that supports outgoing webhooks can send events to Dispatch. Create a source endpoint and point the external service at your webhook URL: ``` POST https://your-api.dispatch.tech/hooks/ ``` ### Authentication Choose one of two methods when configuring the external service: **HMAC signature** — most webhook senders support signing their requests. Set the signing secret on both the external service and your Dispatch endpoint. Dispatch accepts signatures in either of these headers: ``` X-Hub-Signature-256: sha256= X-Dispatch-Signature: sha256= ``` **API key** — if the external service can set custom headers, use a project API key: ``` Authorization: Bearer dsp_your-api-key ``` ### Event Type Extraction If the sending service doesn't include an event type header, set the **Event Type Path** on the endpoint to a dot-notation path into the JSON body (e.g., `action`, `event.type`, or `data.object`). Dispatch will extract the event type from that field automatically. ### Example ```bash BODY='{"action":"opened","issue":{"title":"Bug report"}}' SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "your-signing-secret" | cut -d' ' -f2) curl -X POST https://your-api.dispatch.tech/hooks/my-slug \ -H "Content-Type: application/json" \ -H "X-Dispatch-Signature: sha256=$SIG" \ -d "$BODY" ``` Dispatch responds with `202 Accepted` and an `event_id` once the event is queued. ## Forwarding Webhooks (Destination) Create a **Webhook** destination to forward events to any HTTP endpoint. Dispatch will POST the event payload to the target URL. ### Creating a Webhook Destination 1. Navigate to **Destinations** → **Create Destination** 2. Enter a **name** 3. Select **Webhook** as the type 4. Enter the target URL 5. Optionally filter by event types 6. Click **Create** ### What Gets Sent The raw event payload is forwarded as a JSON `POST` request. If a [transform](/concepts/transforms) is configured on the link, the transformed payload is sent instead. Templates are not applied to webhook destinations — the payload (original or transformed) is always forwarded as-is. Dispatch also injects headers identifying the event: ``` X-Dispatch-Event-ID: X-Dispatch-Endpoint-ID: ``` The prefix (`dispatch`) can be customized per project via the **Header Prefix** setting. ### Retry Behavior Failed deliveries are retried automatically using the destination's retry policy (default: 7 attempts with exponential backoff). See [Destinations](/concepts/destinations#retry-policy) for details. --- # Dashboard Overview The Dispatch dashboard is a web application for managing your webhook infrastructure. It provides a full UI for creating and configuring endpoints, destinations, templates, and monitoring event delivery. ## Navigation The dashboard is organized with a sidebar navigation: | Section | Description | |---------|-------------| | **Organizations** | Top-level view of all your organizations | | **Organization Dashboard** | Usage overview and project list for an org | | **Project Overview** | Quick stats, recent events, and routing visualization | | **Sources** | Manage webhook endpoints | | **Destinations** | Manage delivery targets | | **Templates** | Create and edit message templates | | **Transforms** | Manage reusable JSONata payload transforms | | **Events** | Browse and search events with delivery status | | **Metrics** | Delivery analytics and performance charts | | **Settings** | Project configuration, API keys, and privacy | ## Dashboard Routes | Path | Page | |------|------| | `/dashboard` | Organization list | | `/dashboard/new` | Create organization | | `/dashboard/[orgId]` | Organization dashboard | | `/dashboard/[orgId]/settings` | Organization settings | | `/dashboard/[orgId]/members` | Member management | | `/dashboard/[orgId]/billing` | Billing & subscription | | `/dashboard/[orgId]/subscribe` | Plan selection | | `/projects/[id]/overview` | Project overview | | `/projects/[id]/sources` | Webhook endpoints | | `/projects/[id]/destinations` | Delivery destinations | | `/projects/[id]/templates` | Message templates | | `/projects/[id]/templates/[tid]` | Template editor | | `/projects/[id]/transforms` | JSONata transforms | | `/projects/[id]/transforms/[tid]` | Transform editor | | `/projects/[id]/events` | Event browser | | `/projects/[id]/events/[eid]` | Event detail | | `/projects/[id]/metrics` | Delivery metrics | | `/projects/[id]/settings` | Project settings | --- # Managing Sources The Sources page lets you create and manage webhook endpoints that receive incoming events. ## Creating a Source 1. Click **Create Source** 2. Enter a **name** for the endpoint 3. Optionally set an **event type path** (dot-notation path to extract event type from the body) 4. Choose an **auth method**: signing secret (default) or API key 5. Click **Create** Dispatch generates a unique slug and signing secret. Your webhook URL is: ``` https://your-api.com/hooks/ ``` ## Source Configuration ### Signing Secret The signing secret is shown once at creation. Use it to sign outgoing requests from your external service. You can regenerate the secret at any time, but the old secret immediately becomes invalid. ### Event Type Path Set a dot-notation path (e.g., `action` or `event.type`) to automatically extract the event type from the request body. This is useful when the sending service doesn't include an event type header. ### Active/Inactive Toggle Deactivate a source to stop accepting incoming webhooks. The endpoint URL returns 404 when inactive. Reactivate at any time to resume. ## Linking Destinations From the Sources page, link destinations to an endpoint: 1. Click on a source to expand it 2. Click **Link Destination** 3. Select a destination from the dropdown 4. Click **Link** Once linked, the destination appears under the endpoint. Click the **routing rules** icon (branch icon) on any linked destination to open the full link configuration. ## Link Configuration Each endpoint-destination link has three layers of configuration, all managed from the routing rules dialog: ### Default Template Select a message template to apply to all events delivered to this destination. If no routing rule matches, the default template is used. Leave blank to use the platform's built-in embed format. ### Default Transform Select a named JSONata transform to reshape the payload before it reaches the formatter. Applied when no routing rule matches. Webhook destinations only — for chat destinations (Discord, Slack, Telegram) transforms run before formatting. ### Default Custom Headers For **webhook** destinations only. Key-value pairs added to every outgoing HTTP request. These are overridden per routing rule when a rule specifies its own headers. ## Routing Rules Routing rules let you define conditional logic per link: deliver with a specific template and transform, or skip delivery entirely based on event payload or header values. Rules are evaluated **top-to-bottom**. The first matching rule wins. If no rule matches, the link's defaults apply. ### Adding a Rule In the routing rules dialog, click **Add Rule**, then configure: | Field | Description | |-------|-------------| | **Conditions** | One or more field conditions to match (see below) | | **Logic** | `AND` — all conditions must match; `OR` — any condition can match | | **Action** | `Deliver` — send the event (with optional overrides); `Skip` — drop the event for this destination only | #### Conditions Each condition specifies a field path, an operator, and (for most operators) a value: | Field path format | Example | |-------------------|---------| | `body.` | `body.action`, `body.pull_request.merged` | | `header.` | `header.x-github-event`, `header.x-event-type` | | Operator | Matches when… | |----------|---------------| | `eq` | field equals value | | `neq` | field does not equal value | | `contains` | field contains value (substring) | | `not_contains` | field does not contain value | | `exists` | field is present (no value needed) | | `not_exists` | field is absent (no value needed) | #### Deliver Action — Overrides When the action is **Deliver**, you can optionally override the link defaults for this rule only: - **Template** — use a different template for matching events - **Transform** — use a different transform for matching events - **Custom Headers** — use different HTTP headers (webhook destinations only) Leave any override blank to fall back to the link's default. #### Skip Action When the action is **Skip**, the event is dropped for this destination — no delivery attempt is made. Skipped events are still delivered to other linked destinations that don't skip them. Template, transform, and header fields are ignored when skipping. ### Example ``` Link: GitHub → #deployments (Discord) Default template: "GitHub Default" Rules: 1. body.action = "closed" AND body.pull_request.merged = "true" → Deliver with template "Merged PR" 2. body.ref contains "refs/heads/main" → Deliver with template "Main Branch Push" 3. header.x-github-event = "ping" → Skip ``` A `ping` event is dropped. A merged PR delivers with "Merged PR". A push to main uses "Main Branch Push". Everything else uses "GitHub Default". ## Filters Filters run at the **endpoint** level before any delivery. They control which events are accepted at all — a blocked event is never delivered to any destination. 1. Click **Filters** on the source 2. Click **Add Filter Group** 3. Choose an **action**: - **Allow** — only events matching this group pass through - **Block** — events matching this group are dropped immediately 4. Set the **logic** (AND or OR) and add conditions 5. Enable/disable groups without deleting them ### Evaluation Order Block groups are checked first. If any enabled block group matches, the event is dropped and recorded as filtered. Allow groups are checked next: if any enabled allow groups exist, the event must match at least one of them to proceed. If no allow groups are configured, all non-blocked events pass. > Use **Filters** when you want to suppress events entirely. Use **Routing Rules** when you want per-destination control or want to swap templates and transforms conditionally. See [Filters](/concepts/filters) for a full reference of operators and field paths. ## Integrations Use the integration buttons to connect external services directly. Each integration handles OAuth authorization, endpoint creation, and webhook registration automatically. You can also choose which event types to receive when connecting. | Integration | Description | |-------------|-------------| | **GitHub** | Connect a GitHub repository. Dispatch registers a webhook on the repo and auto-formats push, pull_request, issues, and other GitHub events. | | **GitLab** | Connect a GitLab project via OAuth. Dispatch registers a webhook and auto-formats push, merge request, and issue events. | | **Bitbucket** | Connect a Bitbucket repository via OAuth. Dispatch registers a webhook and auto-formats push and pull request events. | | **Slack** | Connect a Slack workspace. Dispatch creates an app endpoint to receive Slack event callbacks. | | **Jira** | Connect a Jira project via OAuth. Dispatch registers a webhook and auto-formats issue created, updated, and deleted events. | | **Linear** | Connect a Linear workspace via OAuth. Dispatch registers a webhook and auto-formats issue and project events. | | **Stripe** | Connect a Stripe account. Dispatch registers a webhook endpoint and auto-formats charge, payment_intent, subscription, and invoice events. | | **PayPal** | Connect a PayPal account. Enter your PayPal Webhook ID and register the generated URL in the PayPal Developer Dashboard to receive payment, subscription, and dispute events. | | **Confluence** | Connect a Confluence space via OAuth. Dispatch registers a webhook and receives page, blog, and space events. | For detailed setup steps for each integration, see the [Integrations](/integrations) section. ## Testing Click **Send Test** on any source to send a synthetic event through the endpoint. You can provide a custom payload or use the default test payload `{"test": true, "message": "Hello from Dispatch!"}`. --- # Managing Destinations The Destinations page lets you create and manage delivery targets for your events. ## Creating a Destination ### Discord (Manual) 1. Click **Create Destination** 2. Enter a **name** 3. Select **Discord** as the type 4. Paste a Discord webhook URL 5. Optionally filter by event types 6. Click **Create** ### Discord (OAuth) 1. Click **Connect Discord** 2. Authorize the Dispatch bot for your server 3. Select a channel 4. Dispatch creates the webhook and destination automatically ### Slack 1. Click **Connect Slack** 2. Authorize the Dispatch app for your workspace 3. Select a channel 4. Dispatch creates the destination and configures posting to the selected channel ### Telegram Click **Connect** on the Telegram card and choose a mode: **Direct messages** — authenticate with your Telegram account, start a conversation with the bot, then verify DMs are open. **Group or channel** — add the bot to your group or channel, generate a verification code in the dashboard, send the code in the chat, and Dispatch auto-detects it. See [Telegram](/integrations/telegram) for the full step-by-step setup. ### Custom Webhook 1. Click **Create Destination** 2. Enter a **name** 3. Select **Webhook** as the type 4. Enter the target URL 5. Click **Create** ## Event Type Filtering Set the **Event Types** list to only receive specific event types. Leave empty to accept all events. Example: Set event types to `["push", "pull_request"]` to only receive those GitHub events at this destination, even if the endpoint receives other event types. ## Active/Inactive Toggle Deactivate a destination to stop delivering events to it. Events are still received and stored by the endpoint — they're just not delivered to inactive destinations. ## Editing Update the destination name, webhook URL, event type list, or active status at any time. Changes take effect for the next delivery. ## Deleting Deleting a destination removes it and all associated endpoint-destination links. Existing delivery attempt history is preserved. --- # Template Editor The template editor lets you create and customize message templates with a visual editor and live previews for each supported platform. ## Creating a Template 1. Navigate to **Templates** → **Create Template** 2. Enter a name 3. The editor opens with a default template structure ## Editor Layout The template editor has two main panels: - **Left: Platform Tabs + Config Editor** — switch between platform tabs (Discord, Slack, Telegram, Default) and edit the JSON config for each with syntax highlighting - **Right: Live Preview** — see a rendered preview for the active platform tab as you type ### Platform Tabs Each tab represents a platform variant within a v2 multi-platform template: | Tab | Format | Description | |-----|--------|-------------| | **Discord** | Embed config | Standard Discord embed with title, description, fields, color | | **Slack** | Block Kit JSON | Native Slack Block Kit blocks for rich Slack messages | | **Telegram** | HTML template | HTML string for Telegram's HTML parse mode | | **Default** | Embed config | Fallback used when no platform-specific variant is configured | At delivery time, Dispatch selects the variant matching the destination type. If no matching variant is set for that platform, the **Default** variant is used. :::tip You don't need to configure all platforms. Configure only the tabs you need — leave the rest empty and Dispatch will fall back to auto-formatting or the Default tab. ## Discord Tab The Discord tab uses the standard embed config format: ```json { "title": "{{.Meta.EventType}}", "description": "Event from {{.Body.repository.full_name}}", "color": 5763719, "fields": [ { "name": "Action", "value": "{{.Body.action}}", "inline": true } ], "footer": "Dispatch", "thumbnail": "{{.Body.sender.avatar_url}}", "timestamp": true } ``` The live preview renders a Discord embed matching what will be delivered to Discord destinations. ## Slack Tab The Slack tab accepts a `slack_blocks` array containing native [Block Kit](https://api.slack.com/block-kit) blocks. All string values inside the blocks support template expressions: ```json { "slack_blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "{{upper .Meta.EventType}}" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Repository*\n{{.Body.repository.full_name}}" }, { "type": "mrkdwn", "text": "*Action*\n`{{.Body.action}}`" } ] } ] } ``` ## Telegram Tab The Telegram tab accepts a `telegram_html` string that supports template expressions and is sent with Telegram's HTML parse mode: ```json { "telegram_html": "{{upper .Meta.EventType}}\n\nRepo: {{.Body.repository.full_name}}\nAction: {{.Body.action}}\n\nBy {{.Body.sender.login}}" } ``` Supported HTML tags: ``, ``, ``, ``, ``, `
`, ``, ``.

## Variable Discovery

The editor helps you discover available template variables:

- A **Variables** panel shows all available `.Meta` variables (event ID, event type, endpoint ID, timestamp)
- If recent events exist for linked endpoints, the editor shows actual payload paths from real event data so you can click to insert them

This panel is available across all platform tabs.

## Assigning Templates

Templates are assigned per endpoint-destination link:

1. Go to **Sources**
2. Click on an endpoint
3. Edit the destination link
4. Select a template from the dropdown

You can also set up routing rules on a link to use different templates based on event field values. See [Templates](/concepts/templates#routing-rules) for details.

---


# Transforms

The Transforms page lets you create and manage named, reusable JSONata transform expressions for your project.

## What Are Transforms?

A **Transform** is a saved JSONata expression with a name. Once created, a transform can be assigned to any number of endpoint-destination links in your project. This lets you define payload reshaping logic once and reuse it across multiple links without duplicating the expression.

For the conceptual overview of how transforms work, see [Payload Transforms](/concepts/transforms).

## Creating a Transform

1. Navigate to **Transforms** → **Create Transform**
2. Enter a **name** (e.g., "Extract PR fields")
3. The editor opens with an empty expression and a live preview panel

## Transform Editor

The editor has two panels:

- **Left: Expression Editor** — write your JSONata expression with syntax highlighting and validation
- **Right: Live Preview** — paste a sample payload and see the transform output in real time

### Expression Editor

Type your JSONata expression directly. Syntax errors are highlighted inline. Example:

```jsonata
{
  "action": action,
  "repo": repository.full_name,
  "sender": sender.login,
  "pr_title": pull_request.title,
  "pr_url": pull_request.html_url
}
```

### Live Preview

Paste a JSON payload into the input field and the preview pane shows the transformed output. This lets you verify your expression before saving and assigning it to a link.

### AI JSONata Generator

The transform editor includes an AI-powered expression generator. Click the **Generate with AI** button (or press Ctrl+Alt+P), describe what you want in plain English, and Dispatch produces a JSONata expression for you.

For example: *"Extract the PR title, author login, and repository name into a flat object."*

The generated expression is applied to the editor automatically. Review it in the live preview before saving.

## Managing Transforms

From the Transforms list page you can:

- **Rename** a transform by clicking the name inline
- **Edit** the expression by opening the transform editor
- **Delete** a transform (this removes the reference from any links using it — the link will fall back to no transform)

## Assigning a Transform to a Link

After creating a transform, assign it to one or more endpoint-destination links:

1. Go to **Sources**
2. Expand the endpoint and click **Edit** on the destination link
3. Under **Transform**, select the named transform from the dropdown
4. Save the link

To remove a transform from a link, open the link editor and clear the transform selection.

## Routing Rule Overrides

Individual [routing rules](/concepts/templates#routing-rules) on a link can specify a different `transform_id`, allowing you to use a completely different transform for specific event types without changing the link's default.

---


# Event Browser

The Events page provides a powerful interface for browsing, filtering, and inspecting webhook events.

## Browsing Events

The event table shows:

| Column | Description |
|--------|-------------|
| Event Type | Extracted event type (e.g., `push`, `pull_request`) |
| Source | The endpoint that received the event |
| Status | `received` or `filtered` |
| Delivery | Delivery outcome badges (success/retrying/failed count) |
| Created | Timestamp |

## Filtering

### Time Range

Select a preset time range or a custom date range:
- Last 24 hours
- Last 72 hours
- Last 7 days
- Last 30 days
- Custom date range

### Status Filter

Filter by event status:
- **All** — show all events
- **Received** — events that passed filters
- **Filtered** — events blocked by filters

### Source Filter

Filter events by the endpoint that received them.

### Destination Filter

Filter events by which destination they were delivered to.

### Custom Filters

Add conditions on payload fields or headers to narrow results further. Custom filters are applied client-side for real-time results.

### AI Search

Click **AI search** in the toolbar to describe what you're looking for in plain English. Dispatch translates your query into filters, a time range, status, source, and destination automatically.

A preview dialog shows exactly what will be applied before anything changes. Once applied, the generated filters appear as editable chips — you can remove or adjust individual filters just like ones you add manually.

**Examples:**
- "Show me failed GitHub push events from the last 3 days"
- "Events from my production endpoint that went to Ari's server last week"
- "Pull request events from the last 14 days"

## Custom Columns

Add columns for any JSON path in the payload or headers:

1. Click **Add Column**
2. Enter a JSON path (e.g., `body.action` or `header.X-GitHub-Event`)
3. The column appears in the table with values extracted from each event

### Suggested Columns

Dispatch auto-suggests common fields that appear in more than 30% of your events, making it easy to add relevant columns without guessing paths.

## Event Detail View

Click any event to see its full details:

### Payload Tab
- Full JSON payload with syntax highlighting
- Expandable/collapsible nested objects

### Headers Tab
- All request headers in a table
- Sensitive values are masked by default (click to reveal)

### Delivery Attempts
- List of all delivery attempts across all destinations
- Each attempt shows:
  - Attempt number
  - Status (success/failed/retrying)
  - HTTP status code
  - Error message (if failed)
  - Response body and headers
  - Payload that was actually sent
  - Latency in milliseconds
  - Next retry time (if retrying)

## Actions

### Replay Event

Click **Replay** to re-queue the event for delivery to all linked destinations using the current configuration. This creates new delivery attempts without modifying existing ones.

### Copy Payload

Copy the event payload as:
- Raw JSON
- TypeScript type definition
- OpenAPI schema

### Create Template or Transform from Event

From any event's detail view, click the **Create from event** dropdown to bootstrap a new template or transform pre-loaded with the event's payload as the example:

- **New Template** — opens the template editor with this event's payload set as the example. Use it to preview your embed formatting against real data immediately.
- **New Transform** — opens the transform editor with this event's payload set as the example. Write and test your JSONata expression against the actual payload shape without copy-pasting.

After naming the resource and confirming, you are taken directly to its editor.

---


# Delivery Metrics

The Metrics page provides analytics and visualizations for delivery performance.

## Time Ranges

Select a time period for the metrics:
- Last 24 hours
- Last 72 hours
- Last 7 days
- Last 30 days

Time buckets are automatically adjusted based on the selected range.

## Summary Cards

### Success Rate
Percentage of successful deliveries. Color-coded:
- Green: >= 99%
- Yellow: >= 90%
- Red: < 90%

### Average Latency
Mean response time across all delivery attempts in the period.

### Throughput
Average events delivered per hour over the selected period.

### Failed Count
Total number of failed delivery attempts.

## Charts

### Delivery Volume

A stacked bar chart showing:
- **Green bars** — successful deliveries
- **Red bars** — failed deliveries
- **X-axis** — time buckets
- **Y-axis** — delivery count

Hover over bars for detailed counts at each time bucket.

### Latency Over Time

An area chart showing:
- **Blue area** — average latency per time bucket
- **X-axis** — time
- **Y-axis** — latency in milliseconds

### Latency Percentiles

A grid of metric cards showing:
- **p50** — median latency
- **p95** — 95th percentile latency
- **p99** — 99th percentile latency
- **min** — minimum latency
- **max** — maximum latency

---


# Project Settings

The Settings page lets you configure project-level options, manage API keys, and control access.

## General Settings

### Project Name
Update the display name of your project.

### Header Prefix
Set a custom prefix for outgoing headers. Dispatch adds these headers to delivery requests:

- `X-{prefix}-Event-ID` — the event UUID
- `X-{prefix}-Endpoint-ID` — the endpoint UUID

Default prefix: `dispatch`

## API Keys

API keys provide programmatic access to your project's webhook endpoints without HMAC signatures.

### Creating an API Key

1. Click **Create API Key**
2. Enter a descriptive name
3. The key is displayed **once** — copy it immediately

Key format: `dsp_`

### Using API Keys

Include the key in the `Authorization` header when sending webhooks:

```bash
curl -X POST https://api.dispatch.tech/hooks/my-slug \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer dsp_your-key-here" \
  -d '{"event": "test"}'
```

### Revoking an API Key

Click **Revoke** next to any active key. Revoked keys are immediately invalid and cannot be restored.

### Security

- Keys are stored securely — the raw key is never stored after creation
- Only the `dsp_` prefix is visible after creation
- Revoked keys show the revocation timestamp

## Privacy Settings

On paid plans, you can make a project **private**:

1. Toggle the **Private** switch
2. Add members from your organization who should have access
3. Assign roles: admin, developer, or viewer

Organization owners and admins always have access to all projects.

## Danger Zone

### Archive Project

Archiving a project:
- The project no longer appears in listings
- Endpoint URLs return 404
- Event and delivery history is preserved

---


# Billing

The Billing page is available at `/dashboard/[orgId]/billing`. It shows your current plan, subscription status, event usage, and estimated charges for the billing period.

## Current Plan Card

The top of the billing page displays your current plan with:

- **Plan name and price** — e.g., "Team $39/month"
- **Subscription status badge** — one of:
  - `Active` — subscription is active
  - `Trial` — in a trial period
  - `Cancels at period end` — scheduled to cancel but still active
  - `Past Due` — payment failed; service continues temporarily
  - `Canceled` — subscription has ended
  - `Incomplete` — subscription setup was not completed
- **Plan features** — checklist of included limits and features
- **Billing period dates** — shown when active and not canceling

## Event Usage

The usage bar shows how many events you have used in the current billing period:

- **Included events** — counted against your plan limit (shown in the primary color)
- **Overage events** — events past the plan limit on paid plans (shown in amber)

For more on overage billing, see [Organizations & Access Control](/concepts/organizations#overage-billing).

## Estimated Bill

For paid active subscriptions, an **Estimated bill this period** section shows:

| Line Item | Description |
|-----------|-------------|
| Subscription | Base plan price (e.g., $39.00) |
| Overage events | Number of overage events × $0.03/1k |
| **Total estimate** | Sum of subscription + overage |

> Final amount may differ. Overage is calculated and billed at period end.

This section is only shown for paid plans with an active or trialing subscription.

## Cancellation & Downgrade Notices

If your subscription is scheduled to cancel or downgrade at the end of the current period, a notice appears showing:

- **Canceling** — "Your plan is scheduled to cancel on [date]. You'll keep your current limits until then."
- **Downgrading** — "Switching to [new plan] on [date]. You'll keep [current plan] limits until then."

## Actions

| Button | Description |
|--------|-------------|
| **Manage Subscription** | Opens the Stripe customer portal to update payment details, view invoices, or cancel |
| **Change Plan** | Navigate to the plan selection page to upgrade or switch plans |
| **Resubscribe / Upgrade Plan** | Shown for free, canceled, or past-due plans — links to plan selection |

## Plan Comparison

See [Organizations & Access Control](/concepts/organizations#plans) for a full comparison of Free, Team, and Growth plan limits and pricing.

---


# Real-Time Updates

The Dispatch dashboard uses Supabase Realtime to provide live updates without polling.

## How It Works

When you're viewing the Events page, the dashboard subscribes to database changes via Supabase Realtime. New events and delivery status updates appear instantly without refreshing.

## What Updates in Real-Time

| Data | Trigger |
|------|---------|
| Events list | New event received at any project endpoint |
| Delivery status | Delivery attempt succeeds, fails, or is retried |
| Event detail | New delivery attempts appear for a viewed event |

## Subscription Scope

Realtime subscriptions are scoped to the current project. Only events and deliveries belonging to the project you're viewing are streamed.

## Fallback

If the Realtime connection drops, the dashboard falls back to manual refresh. Use the **Refresh** button on the Events page to fetch the latest data.

---


# MCP Server

The Dispatch MCP (Model Context Protocol) server lets AI assistants manage your webhooks directly. You can list sources, create destinations, inspect events, and configure filters and transforms through natural language in tools like Claude, Cursor, Windsurf, and other MCP-compatible clients.

## Prerequisites

- A Dispatch account with a project
- An API key (created in **Project Settings > API Keys**)
- Node.js 18 or later

## Installation

```bash
npm install -g @dispatch.tech/mcp
```

Or run it directly with `npx` (no global install needed):

```bash
npx @dispatch.tech/mcp
```

## Configuration

The MCP server requires two environment variables:

| Variable | Required | Description |
|----------|----------|-------------|
| `DISPATCH_API_KEY` | Yes | Your project API key (starts with `dsp_`) |
| `DISPATCH_API_URL` | Yes | The Dispatch API URL (e.g., `https://api.dispatch.tech`) |

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "dispatch": {
      "command": "npx",
      "args": ["@dispatch.tech/mcp"],
      "env": {
        "DISPATCH_API_KEY": "dsp_your_api_key_here",
        "DISPATCH_API_URL": "https://api.dispatch.tech"
      }
    }
  }
}
```

### Claude Code

```bash
claude mcp add dispatch \
  -e DISPATCH_API_KEY=dsp_your_api_key_here \
  -e DISPATCH_API_URL=https://api.dispatch.tech \
  -- npx @dispatch.tech/mcp
```

### Cursor / Windsurf

Add to your project's `.cursor/mcp.json` or `.windsurf/mcp.json`:

```json
{
  "mcpServers": {
    "dispatch": {
      "command": "npx",
      "args": ["@dispatch.tech/mcp"],
      "env": {
        "DISPATCH_API_KEY": "dsp_your_api_key_here",
        "DISPATCH_API_URL": "https://api.dispatch.tech"
      }
    }
  }
}
```

### VS Code

Add to `.vscode/mcp.json`:

```json
{
  "servers": {
    "dispatch": {
      "command": "npx",
      "args": ["@dispatch.tech/mcp"],
      "env": {
        "DISPATCH_API_KEY": "dsp_your_api_key_here",
        "DISPATCH_API_URL": "https://api.dispatch.tech"
      }
    }
  }
}
```

## Available Tools

### Sources

| Tool | Description |
|------|-------------|
| `list_sources` | List all webhook sources (endpoints) in the project |
| `create_source` | Create a new webhook source |
| `get_source` | Get details of a specific source |
| `update_source` | Update a source's name or active status |
| `delete_source` | Delete a source and all its connections |

### Destinations

| Tool | Description |
|------|-------------|
| `list_destinations` | List all destinations in the project |
| `create_destination` | Create a new webhook destination |
| `get_destination` | Get details of a specific destination |
| `delete_destination` | Delete a destination and all its connections |

### Connections

| Tool | Description |
|------|-------------|
| `list_connections` | List connections for a source |
| `create_connection` | Connect a source to a destination |
| `delete_connection` | Remove a connection between a source and destination |

### Events

| Tool | Description |
|------|-------------|
| `list_events` | List recent webhook events with pagination and filtering |
| `get_event` | Get full event details including delivery attempts |

### Templates

| Tool | Description |
|------|-------------|
| `list_templates` | List all message templates |
| `create_template` | Create a message template for formatting payloads |

### Transforms

| Tool | Description |
|------|-------------|
| `list_transforms` | List all JSONata transforms |
| `create_transform` | Create a JSONata transform expression |

### Filters

| Tool | Description |
|------|-------------|
| `list_filters` | List filter groups for a source |
| `create_filter` | Create a filter group with conditions |
| `delete_filter` | Delete a filter group |

## Example Usage

Once configured, you can interact with Dispatch through your AI assistant:

- "List all my webhook sources"
- "Create a new source called Stripe"
- "Show me the last 10 events"
- "Connect my GitHub source to my Discord destination"
- "Create a filter on my GitHub source to only accept push events to main"
- "What events came in today?"

## Generating an API Key

1. Open the Dispatch dashboard
2. Navigate to your project's **Settings** page
3. Click **API Keys** > **Create Key**
4. Copy the key (it starts with `dsp_` and is only shown once)
5. Use this key as the `DISPATCH_API_KEY` environment variable

---