---
title: "Create a delivery binding"
method: POST
path: "/v1/delivery/bindings"
tags: ["Delivery"]
---

# Create a delivery binding

`POST /v1/delivery/bindings`

## Request

- Base URL: `https://api.talonic.com`
- URL: `https://api.talonic.com/v1/delivery/bindings`
- Auth: HTTP bearer

Runs the compatibility-triangle validator: the `signal_filter` must be
well-formed, the `deliverable_type` must resolve to a registered
resolver, the `serializer_format` must resolve to a registered
serializer, and the serializer must support the resolver's shape.

## Request body

- CreateBindingRequest
  - `name` string, required
  - `signal_filter` SignalFilter, required: Shallow predicate stored on each binding. Slice-1 matching is exact event_type + equality on optional `match` keys; richer predicates are intentionally out of scope.
    - `event_type` string, required
    - `match` object, nullable: Optional equality filter on payload keys — EXCEPT on a `dispatch.*` binding, where `match.spec_id` AND `match.stage_id` are REQUIRED (400 otherwise). A Dispatch binding targets one Dispatch node of one Spec; an absent or empty `match` matches every event, so without both keys the binding would receive every Dispatch node's events tenant-wide. `stage_id` alone is not tenant-unique — an id-less rail node normalizes to `s-dispatch` — hence `spec_id` too. Every `dispatch.*` payload carries both.
  - `deliverable_type` string, required
  - `destination_id` string, uuid, required
  - `field_map` FieldMap: Declarative projection applied between resolver output and serializer input. Operations run in fixed order: drop → rename → static.
    - `rules` object[]
      - `source` string, required
      - `target` string, required
    - `static` object: Literal key/value pairs added last — always wins collisions.
    - `drop` string[]
  - `serializer_format` string, required
  - `serializer_config` object
  - `resolver_config` object: Per-binding resolver options — e.g. `{"null_handling": "distinguish"}` on a `pipeline.capture` binding. Sparse modes (`omit_absent`, `distinguish`) require a json/ndjson serializer (400 otherwise).
  - `excluded_fields` string[]: Field keys omitted from every delivered payload (binding-level egress filter, honored by `pipeline.capture`). Empty array delivers everything.
  - `delivery_policy` DeliveryPolicy: Retry / timeout overrides consumed by the TransportWrapper, plus the per-binding wire-compatibility knobs read by the webhook connector. Slice 1 defaults to a 6-attempt ladder with backoff `[5s, 30s, 2min, 10min, 1h]`.
    - `wire_format` 'v1' | 'legacy_v1_webhooks': Body shape a `webhook` destination receives. `v1` (default) is the delivery envelope `{ event, payload }`. `legacy_v1_webhooks` reproduces the retired `/v1/webhooks` body — `{ event, delivery_id, timestamp, data }`, with `event` mapped back to the legacy name (`pipeline.completed` → `run.completed`, `pipeline.failed` → `run.failed`) and `delivery_id` = `dlv_` + the first 16 chars of the idempotency key (stable across attempts, unlike the legacy random-per-fire id) — so a receiver migrated off `/v1/webhooks` parses it unchanged. Legacy values are rejected (400) on a non-`webhook` destination, and an unknown value is a 400 rather than a silent fallback to `v1`.
    - `signature_scheme` 'v1' | 'legacy_sha256': How `X-Talonic-Signature` is computed for a `webhook` destination carrying a signing secret. `v1` (default) signs `t=<ms>,v1=<hmac(ts.body)>`. `legacy_sha256` signs `sha256=<hmac(body)>` and adds the legacy `X-Talonic-Event`, `X-Talonic-Delivery` and `User-Agent: Talonic-Webhooks/1.0` headers (the `v1` headers are still sent). Legacy values are rejected (400) on a non-`webhook` destination; an unknown value is a 400.
    - `max_attempts` integer
    - `backoff_schedule` integer[]
    - `timeout_ms` integer
    - `rate_limit` object
      - `ratePerSec` number
      - `capacity` integer
  - `is_active` boolean

## Response `200`

Binding created (alias of 201).

- DeliveryBinding
  - `id` string, uuid, required
  - `customer_id` string, uuid, required
  - `name` string, required
  - `signal_filter` SignalFilter, required: Shallow predicate stored on each binding. Slice-1 matching is exact event_type + equality on optional `match` keys; richer predicates are intentionally out of scope.
    - `event_type` string, required
    - `match` object, nullable: Optional equality filter on payload keys — EXCEPT on a `dispatch.*` binding, where `match.spec_id` AND `match.stage_id` are REQUIRED (400 otherwise). A Dispatch binding targets one Dispatch node of one Spec; an absent or empty `match` matches every event, so without both keys the binding would receive every Dispatch node's events tenant-wide. `stage_id` alone is not tenant-unique — an id-less rail node normalizes to `s-dispatch` — hence `spec_id` too. Every `dispatch.*` payload carries both.
  - `deliverable_type` string, required: Must resolve to a registered deliverable resolver.
  - `destination_id` string, uuid, nullable, required: Null when the destination was deleted: the binding is orphaned (inactive, last_status destination_deleted) until re-pointed.
  - `field_map` FieldMap: Declarative projection applied between resolver output and serializer input. Operations run in fixed order: drop → rename → static.
    - `rules` object[]
      - `source` string, required
      - `target` string, required
    - `static` object: Literal key/value pairs added last — always wins collisions.
    - `drop` string[]
  - `serializer_format` string, required: Must resolve to a registered serializer (json, ndjson, csv, csv_file, xlsx, rows, graph, raw, md, txt).
  - `serializer_config` object
  - `resolver_config` object, nullable: Per-binding resolver options threaded into the deliverable resolver. First consumer: `null_handling` on the `pipeline.capture` deliverable — `always_null` (default: every declared schema field present, missing values as null), `omit_absent` (valued fields only), or `distinguish` (null = deliberately cleared by resolution; never-captured keys omitted). The sparse modes require a json or ndjson serializer (compatibility validated on create/update).
  - `excluded_fields` string[], nullable: Field keys OMITTED from every payload this binding delivers — the binding-level egress filter (honored by `pipeline.capture` in all null_handling modes). Excluded fields stay available to downstream pipeline stages (data product, business rules); null/empty delivers everything.
  - `delivery_policy` DeliveryPolicy: Retry / timeout overrides consumed by the TransportWrapper, plus the per-binding wire-compatibility knobs read by the webhook connector. Slice 1 defaults to a 6-attempt ladder with backoff `[5s, 30s, 2min, 10min, 1h]`.
    - `wire_format` 'v1' | 'legacy_v1_webhooks': Body shape a `webhook` destination receives. `v1` (default) is the delivery envelope `{ event, payload }`. `legacy_v1_webhooks` reproduces the retired `/v1/webhooks` body — `{ event, delivery_id, timestamp, data }`, with `event` mapped back to the legacy name (`pipeline.completed` → `run.completed`, `pipeline.failed` → `run.failed`) and `delivery_id` = `dlv_` + the first 16 chars of the idempotency key (stable across attempts, unlike the legacy random-per-fire id) — so a receiver migrated off `/v1/webhooks` parses it unchanged. Legacy values are rejected (400) on a non-`webhook` destination, and an unknown value is a 400 rather than a silent fallback to `v1`.
    - `signature_scheme` 'v1' | 'legacy_sha256': How `X-Talonic-Signature` is computed for a `webhook` destination carrying a signing secret. `v1` (default) signs `t=<ms>,v1=<hmac(ts.body)>`. `legacy_sha256` signs `sha256=<hmac(body)>` and adds the legacy `X-Talonic-Event`, `X-Talonic-Delivery` and `User-Agent: Talonic-Webhooks/1.0` headers (the `v1` headers are still sent). Legacy values are rejected (400) on a non-`webhook` destination; an unknown value is a 400.
    - `max_attempts` integer
    - `backoff_schedule` integer[]
    - `timeout_ms` integer
    - `rate_limit` object
      - `ratePerSec` number
      - `capacity` integer
  - `is_active` boolean, required
  - `last_status` string, nullable
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required

## Other responses

- `201`: Binding created.
- `400`: The request is invalid.
- `401`: Missing or invalid API key.
- `429`: Daily rate limit exceeded.

---

[API](https://skmtc.dev/talonic/apis/talonic-api.md) · [All operations](https://skmtc.dev/talonic/apis/talonic-api/llms.txt) · [OpenAPI document](https://skmtc.dev/talonic/apis/talonic-api/revisions/678fe5f8c9c3?raw)
