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

# Destinations and deliveries

> Configure where events are sent, track delivery status, and handle retries.

## Event destinations

A destination defines where events are delivered. Deck supports multiple destination types so you can receive events wherever your infrastructure already lives.

### Supported destination types

| Type | Identifier | Description |
| - | - | - |
| **Webhook** | `webhook` | HTTP endpoint that receives `POST` requests with [Standard Webhooks](https://www.standardwebhooks.com/) signatures |
| **AWS SQS** | `aws_sqs` | Amazon Simple Queue Service queue |
| **AWS Kinesis** | `aws_kinesis` | Amazon Kinesis Data Stream |
| **AWS S3** | `aws_s3` | Amazon S3 bucket (events stored as JSON objects) |
| **AWS EventBridge** | `aws_eventbridge` | Amazon EventBridge event bus |
| **GCP Pub/Sub** | `gcp_pubsub` | Google Cloud Pub/Sub topic |
| **Azure Service Bus** | `azure_servicebus` | Azure Service Bus queue or topic |
| **RabbitMQ** | `rabbitmq` | AMQP-compatible message broker exchange |
| **Hookdeck** | `hookdeck` | Hookdeck Event Gateway for routing, filtering, and transforming events |
| **Cloudflare Queues** | `cloudflare_queues` | Cloudflare Queues queue |

### Creating a destination

To create a destination, send a `POST` to `/v2/event-destinations` with the destination `type`, a type-specific object (keyed by the type name) containing both config fields and credentials, and the events you want to receive.

```json theme={null}
POST /v2/event-destinations

{
  "name": "Production Webhook",
  "type": "webhook",
  "webhook": {
    "url": "https://your-app.com/webhooks/deck"
  },
  "events": ["credential.verified", "task_run.completed", "task_run.failed"]
}
```

Use `"events": ["*"]` to subscribe to everything.

Credential fields go inside the type-keyed object alongside config fields. There is no separate top-level `credentials` block.

### Filtering events

Subscribing by event type is enough for most destinations. To narrow deliveries further, add an optional `filter`. Deck matches it against each event payload and delivers only the events that match. For example, to send one task's runs to their own destination:

```json theme={null}
POST /v2/event-destinations

{
  "name": "Invoice task runs",
  "type": "webhook",
  "webhook": {
    "url": "https://your-app.com/webhooks/deck-invoices"
  },
  "events": ["task_run.completed", "task_run.failed"],
  "filter": {
    "data": {
      "task_id": "task_a1b2c3d4"
    }
  }
}
```

Write the filter against the event as you receive it, starting from its top-level fields such as `type` and `data`. Fields listed together must all match. A filter applies to every delivery to the destination, including retries.

Filters support these operators:

| Operator | Matches when |
| - | - |
| `$eq`, `$neq` | The field equals, or doesn't equal, the value |
| `$gt`, `$gte`, `$lt`, `$lte` | The field compares greater or less than the value |
| `$in`, `$nin` | The field is, or isn't, one of the listed values |
| `$startsWith`, `$endsWith` | The string field starts or ends with the value |
| `$exist` | The field is present (`true`) or absent (`false`) |
| `$and`, `$or`, `$not` | Combine or negate conditions |

For example, this filter delivers runs from either of two agents:

```json theme={null}
{
  "filter": {
    "data": {
      "agent_id": { "$in": ["agt_a1b2c3d4", "agt_e5f6g7h8"] }
    }
  }
}
```

A filter can only match fields the event carries. Credential, session, and source events don't include a task or agent ID, so you can't route them by task or agent. To change a filter, send a new `filter` with `PATCH /v2/event-destinations/{destination_id}`. It replaces the existing one. Send `null` or `{}` to remove it.

***

## Destination configuration reference

Each destination type requires specific configuration fields and credentials.

> [tab] Webhook
>
> Delivers events as HTTP `POST` requests to a URL endpoint. Signed using the [Standard Webhooks](https://www.standardwebhooks.com/) specification.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `webhook.url` | string | Yes | Absolute endpoint URL. HTTP is accepted; use HTTPS for production. |
> | `webhook.custom_headers` | string | No | JSON-encoded object of additional headers to include on every request |
> | `webhook.secret` | string | No | Signing secret (`whsec_` prefix) used to verify delivery signatures. Optional and caller-provided. Supply your own if you want to verify signatures. It's stored securely and **cannot be retrieved after creation**, so save it somewhere safe when you create the destination. |
>
> ```json theme={null}
> {
>   "type": "webhook",
>   "webhook": {
>     "url": "https://your-app.com/webhooks/deck"
>   },
>   "events": ["*"]
> }
> ```

> [tab] AWS SQS
>
> Delivers events as messages to an Amazon SQS queue. Event metadata is included as message attributes.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `aws_sqs.queue_url` | string | Yes | Full SQS queue URL |
> | `aws_sqs.key` | string | No | AWS Access Key ID |
> | `aws_sqs.secret` | string | No | AWS Secret Access Key |
> | `aws_sqs.session` | string | No | Session token for temporary credentials |
>
> **Required IAM permission:** `sqs:SendMessage` on the queue resource.
>
> ```json theme={null}
> {
>   "type": "aws_sqs",
>   "aws_sqs": {
>     "queue_url": "https://sqs.us-east-1.amazonaws.com/123456789012/deck-events",
>     "key": "AKIAIOSFODNN7EXAMPLE",
>     "secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
>   },
>   "events": ["*"]
> }
> ```

> [tab] AWS Kinesis
>
> Streams events to an Amazon Kinesis Data Stream. The event ID is used as the partition key.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `aws_kinesis.stream_name` | string | Yes | Kinesis stream name |
> | `aws_kinesis.region` | string | Yes | AWS region (e.g., `us-east-1`) |
> | `aws_kinesis.key` | string | No | AWS Access Key ID |
> | `aws_kinesis.secret` | string | No | AWS Secret Access Key |
> | `aws_kinesis.session` | string | No | Session token for temporary credentials |
>
> **Required IAM permission:** `kinesis:PutRecord` on the stream resource.
>
> ```json theme={null}
> {
>   "type": "aws_kinesis",
>   "aws_kinesis": {
>     "stream_name": "deck-events",
>     "region": "us-east-1",
>     "key": "AKIAIOSFODNN7EXAMPLE",
>     "secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
>   },
>   "events": ["*"]
> }
> ```

> [tab] AWS S3
>
> Stores events as JSON objects in an Amazon S3 bucket. Each event becomes a separate object keyed by `{rfc3339_nano_timestamp}_{event_id}.json`.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `aws_s3.bucket` | string | Yes | S3 bucket name |
> | `aws_s3.region` | string | Yes | AWS region (e.g., `us-east-1`) |
> | `aws_s3.storage_class` | string | No | Any [S3 storage class](https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html) (e.g., `STANDARD`, `STANDARD_IA`, `INTELLIGENT_TIERING`, `GLACIER_IR`, `GLACIER`, `DEEP_ARCHIVE`). Defaults to `STANDARD`. |
> | `aws_s3.key` | string | No | AWS Access Key ID |
> | `aws_s3.secret` | string | No | AWS Secret Access Key |
> | `aws_s3.session` | string | No | Session token for temporary credentials |
>
> **Required IAM permission:** `s3:PutObject` on the bucket resource.
>
> ```json theme={null}
> {
>   "type": "aws_s3",
>   "aws_s3": {
>     "bucket": "deck-events",
>     "region": "us-east-1",
>     "key": "AKIAIOSFODNN7EXAMPLE",
>     "secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
>   },
>   "events": ["*"]
> }
> ```

> [tab] AWS EventBridge
>
> Publishes events to an Amazon EventBridge event bus.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `aws_eventbridge.event_bus_name` | string | No | Event bus name or ARN. Defaults to the account's default event bus |
> | `aws_eventbridge.region` | string | Yes | AWS region (e.g., `us-east-1`) |
> | `aws_eventbridge.key` | string | No | AWS Access Key ID |
> | `aws_eventbridge.secret` | string | No | AWS Secret Access Key |
> | `aws_eventbridge.session` | string | No | Session token for temporary credentials |
>
> **Required IAM permission:** `events:PutEvents` on the event bus resource.
>
> ```json theme={null}
> {
>   "type": "aws_eventbridge",
>   "aws_eventbridge": {
>     "event_bus_name": "deck-events",
>     "region": "us-east-1",
>     "key": "AKIAIOSFODNN7EXAMPLE",
>     "secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
>   },
>   "events": ["*"]
> }
> ```

> [tab] GCP Pub/Sub
>
> Publishes events to a Google Cloud Pub/Sub topic. Event metadata is included as message attributes.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `gcp_pubsub.project_id` | string | Yes | GCP project ID |
> | `gcp_pubsub.topic` | string | Yes | Pub/Sub topic name |
> | `gcp_pubsub.service_account_json` | string | No | Service account JSON key (stringified) |
>
> **Required IAM role:** `roles/pubsub.publisher` on the topic.
>
> ```json theme={null}
> {
>   "type": "gcp_pubsub",
>   "gcp_pubsub": {
>     "project_id": "my-gcp-project",
>     "topic": "deck-events",
>     "service_account_json": "{\"type\": \"service_account\", ...}"
>   },
>   "events": ["*"]
> }
> ```

> [tab] Azure Service Bus
>
> Sends events to an Azure Service Bus queue or topic. Event metadata is included as application properties.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `azure_servicebus.name` | string | Yes | Queue or topic name |
> | `azure_servicebus.connection_string` | string | No | Service Bus connection string with `Send` permission |
>
> ```json theme={null}
> {
>   "type": "azure_servicebus",
>   "azure_servicebus": {
>     "name": "deck-events",
>     "connection_string": "Endpoint=sb://my-namespace.servicebus.windows.net/;SharedAccessKeyName=sender;SharedAccessKey=..."
>   },
>   "events": ["*"]
> }
> ```

> [tab] RabbitMQ
>
> Publishes events to a RabbitMQ exchange via AMQP. The event type is used as the routing key. Event metadata is included as message headers.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `rabbitmq.server_url` | string | Yes | RabbitMQ host and port (e.g., `rabbitmq.example.com:5672`). A full `amqp://user:pass@host:port` URL is also accepted; credentials and host are extracted from it. |
> | `rabbitmq.exchange` | string | Yes | Exchange name |
> | `rabbitmq.tls` | string | No | Enable TLS. Accepts `"true"`, `"on"`, or `"false"`. Defaults to `"false"`. |
> | `rabbitmq.username` | string | Yes¹ | RabbitMQ username |
> | `rabbitmq.password` | string | Yes¹ | RabbitMQ password |
>
> ¹ Required unless embedded in `server_url`.
>
> The exchange must already exist. Deck publishes messages with the event type as the routing key, so you can bind queues using topic patterns.
>
> ```json theme={null}
> {
>   "type": "rabbitmq",
>   "rabbitmq": {
>     "server_url": "rabbitmq.example.com:5672",
>     "exchange": "deck-events",
>     "username": "deck",
>     "password": "..."
>   },
>   "events": ["*"]
> }
> ```

> [tab] Hookdeck
>
> Routes events through the Hookdeck Event Gateway for advanced routing, filtering, and transformation before delivery. Routing and delivery settings are managed in the Hookdeck dashboard.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `hookdeck.token` | string | Yes | Hookdeck source token |
>
> ```json theme={null}
> {
>   "type": "hookdeck",
>   "hookdeck": {
>     "token": "<HOOKDECK_SOURCE_TOKEN>"
>   },
>   "events": ["*"]
> }
> ```

> [tab] Cloudflare Queues
>
> Sends events as messages to a Cloudflare queue.
>
> | Field | Type | Required | Description |
> | - | - | - | - |
> | `cloudflare_queues.account_id` | string | Yes | Cloudflare account ID |
> | `cloudflare_queues.queue_id` | string | Yes | Queue ID |
> | `cloudflare_queues.api_token` | string | No | Cloudflare API token |
>
> **Required token permission:** write access to Queues on the account.
>
> ```json theme={null}
> {
>   "type": "cloudflare_queues",
>   "cloudflare_queues": {
>     "account_id": "023e105f4ecef8ad9ca31a8372d0c353",
>     "queue_id": "df8d1e3b2a7c4f6e9b0a1c2d3e4f5a6b",
>     "api_token": "<CLOUDFLARE_API_TOKEN>"
>   },
>   "events": ["*"]
> }
> ```

### Verifying webhook signatures

Deck uses the [Standard Webhooks](https://www.standardwebhooks.com/) specification for webhook signatures. Every delivery includes headers for verification. To verify signatures, provide a `whsec_` signing secret when you create the webhook destination. The secret is stored securely and **cannot be retrieved afterward**, so save it somewhere safe at creation time.

#### Headers sent with each delivery

| Header | Description |
| - | - |
| `webhook-id` | Unique event identifier |
| `webhook-timestamp` | Unix timestamp of when the delivery was sent |
| `webhook-signature` | Base64-encoded HMAC-SHA256 signature in `v1,<signature>` format |

#### Verification

Use the official [Standard Webhooks SDK](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries) to verify signatures. SDKs are available for JavaScript, Python, Go, Ruby, Java, Rust, and more.

```javascript Node.js theme={null}
import { Webhook } from "standardwebhooks";

const wh = new Webhook(process.env.DECK_WEBHOOK_SECRET);

app.post("/webhooks/deck", (req, res) => {
  try {
    const payload = wh.verify(req.rawBody, req.headers);
    await processEvent(payload);
    res.status(200).json({ received: true });
  } catch (err) {
    res.status(401).json({ error: "Invalid signature" });
  }
});
```

```python Python theme={null}
from standardwebhooks.webhooks import Webhook

wh = Webhook(os.environ["DECK_WEBHOOK_SECRET"])

def handle_webhook(request):
    try:
        payload = wh.verify(request.body, request.headers)
        process_event(payload)
        return {"received": True}, 200
    except Exception:
        return {"error": "Invalid signature"}, 401
```

```go Go theme={null}
import svix "github.com/standard-webhooks/standard-webhooks/libraries/go"

func handleWebhook(w http.ResponseWriter, r *http.Request) {
	wh, _ := svix.NewWebhook(os.Getenv("DECK_WEBHOOK_SECRET"))

	body, _ := io.ReadAll(r.Body)
	err := wh.Verify(body, r.Header)
	if err != nil {
		http.Error(w, "Invalid signature", http.StatusUnauthorized)
		return
	}

	processEvent(body)
	w.WriteHeader(http.StatusOK)
}
```

#### Secret rotation

When you rotate your signing secret, Deck keeps the previous secret valid for 24 hours. During this window, verify against both secrets so you can roll over without downtime.

### Static IP addresses

Webhook deliveries come from a fixed set of static IP addresses. If your endpoint sits behind a firewall or IP allowlist, add these addresses so deliveries aren't blocked.

* `34.139.110.242`
* `35.237.129.153`
* `34.24.255.163`

Static delivery IPs apply to webhook destinations only. Cloud destinations such as SQS, EventBridge, Pub/Sub, Service Bus, and Cloudflare Queues authenticate with their native credentials.

### Destination statuses

| Status | Meaning |
| - | - |
| `pending_verification` | Deck is waiting for a successful response from your endpoint |
| `active` | Receiving deliveries |
| `inactive` | Not receiving deliveries, either because you set it or [automatically](#automatic-deactivation) after repeated delivery failures. Set `status: "active"` via `PATCH /v2/event-destinations/{id}` to resume deliveries. |

## Testing a destination

### Test endpoints

If you don't have a receiver ready yet, you can still create a destination and watch events arrive. In the Console's create flow, choose **Test endpoint** instead of entering your own URL. Deck provisions a disposable [Hookdeck Console](https://console.hookdeck.com) URL as the destination's webhook URL, and an **Inspect deliveries** button on the destination opens delivered payloads in your browser. No receiver code required.

The API doesn't create test endpoints, but it recognizes them. A destination whose URL is a Hookdeck Console endpoint includes a read-only `test_endpoint` object with the inspect URL:

```json theme={null}
{
  "id": "evtd_a1b2c3d4",
  "type": "webhook",
  "test_endpoint": {
    "inspect_url": "https://console.hookdeck.com/abc123"
  }
}
```

> [!WARNING]
>
> Anyone with the inspect link can read deliveries. Use test endpoints for test data only, and switch to your own endpoint before sending real events.

### Sending a test event

Once a destination is active, send a `test.ping` event to confirm deliveries reach it. Use the **Send test ping** button on the destination in the Console, or call `POST /v2/event-destinations/{destination_id}/test`. The response reports the delivery attempt:

```json theme={null}
{
  "status": "success",
  "event_id": "evt_a1b2c3d4",
  "response_code": 200
}
```

| Field | Description |
| - | - |
| `status` | `success`, `failure`, or `pending` if the attempt hasn't resolved yet. Check [deliveries](#deliveries) for the final status. |
| `event_id` | ID of the `test.ping` event that was sent |
| `response_code` | HTTP status code returned by the endpoint, when available |

The test event's `data` payload is `{"ok": true}`. Test events are delivered once and not retried. If the destination has a [filter](#filtering-events) that excludes `test.ping`, the event isn't delivered and the status stays `pending`. A destination still in `pending_verification` can't receive test events.

## Deliveries

Each delivery is tracked independently. For webhooks, a delivery fails when the destination does not return a `2xx` response within **5 seconds**.

Failed deliveries are retried up to **10 times** with exponential backoff (base 2, starting at 30 seconds).

List failed deliveries with `GET /v2/event-destinations/{id}/event-deliveries?status=failure` to inspect them.

### Automatic deactivation

If deliveries to a destination fail 100 times in a row, Deck sets the destination to `inactive` and stops delivering to it. A successful delivery resets the count.

When the destination is deactivated, Deck sends an `event_destination.deactivated` event. Subscribe a different destination to this event, since the failing one may not receive it.

To resume deliveries, fix the endpoint, then set `status: "active"` with `PATCH /v2/event-destinations/{id}`.

### Delivery statuses

| Status | Meaning |
| - | - |
| `success` | Delivered and acknowledged |
| `failure` | All retry attempts exhausted |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.