Data Models
This page documents the core data structures used throughout the Dispatch API.
Project
{
"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
{
"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
{
"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)
{
"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):
{
"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):
{
"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": "<b>{{.Meta.EventType}}</b>\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 for the full template config reference.
Transform
{
"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
{
"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
{
"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
{
"id": "uuid",
"endpoint_id": "uuid",
"name": "Accept pushes to main",
"logic": "AND",
"is_enabled": true,
"conditions": []
}
FilterCondition
{
"id": "uuid",
"filter_id": "uuid",
"field": "body.ref",
"operator": "eq",
"value": "refs/heads/main",
"position": 0
}
APIKey
{
"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
{
"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