# Attach Webhook

`POST /webhooks/attach`

Attach a webhook to one or more senders (phone numbers or RCS agent IDs) to receive real-time event notifications.

You can attach an existing webhook by providing its ID, or create a new webhook by specifying a name and URL. Supports bulk operations with up to 50 senders per request.

Subscriptions are additive — attaching new senders does not remove existing ones. Re-attaching the same sender updates the event type filter without creating duplicates.

**Custom headers** may be provided in either case via the optional `headers` field. When attaching a new webhook, the headers are stored on the webhook and sent on every delivery. When attaching an existing `webhookId`, supplying `headers` **overwrites** the stored headers on that webhook — omit the field to leave them unchanged, or pass an empty object `{}` to clear them. The reserved `PINNACLE-SIGNING-SECRET` header is always set by Pinnacle and cannot be overridden.

## Authentication

Send your API key in the `PINNACLE-API-KEY` header.

## Request body

Required.

Content type: `application/json`.

### Fields

- `senders` — `string[]`. Array of senders to attach the webhook to. Can be phone numbers in E.164 format or RCS agent IDs.
- `webhookId` — `string`, optional. Existing webhook ID (starts with `wh_`). Provide this OR `name` + `url` to create a new webhook. The webhook must be in ENABLED status. Disabled webhooks can be re-enabled from the dashboard.

Supplying `headers` alongside `webhookId` **overwrites** the stored headers on the webhook. Omit `headers` to leave them unchanged.
- `name` — `string`, optional, minimum length: 1. Name for a new webhook (required if no `webhookId`).
- `url` — `string`, optional, format: uri. HTTPS endpoint URL for a new webhook (required if no `webhookId`).
- `event` — `null | WebhookEventEnum`, optional, default: null. Event type filter for the subscription. Set to `null` to receive all events.

`USER.TYPING` and `CAMPAIGN.STATUS` are only supported for RCS agent senders, not phone numbers — attempting to attach either of these events to a phone number returns `400 Bad Request`.
- `event` Variant: variant 1
- `event` Variant: variant 2
- `headers` — `object`, optional. Optional custom HTTP headers (key-value map) to include when dispatching webhook events to the endpoint.

Header names must start with a letter or digit and contain only letters, digits, `-`, or `_` (matching the pattern `^[A-Za-z0-9][A-Za-z0-9_-]*$`). Names are case-insensitive per RFC 9110 and are normalized to uppercase before storage and sending.

When provided with an existing `webhookId`, these headers **overwrite** any headers currently stored on that webhook. Omit to leave existing headers unchanged.

The reserved `PINNACLE-SIGNING-SECRET` header is silently ignored and cannot be overridden.

### Example

```json
{
  "senders": [
    "+14155551234",
    "agent_abc123"
  ],
  "webhookId": "webhook_01J8K3R7M9",
  "name": "Acme Support",
  "url": "https://example.com/webhooks/pinnacle",
  "event": null,
  "headers": {
    "X-API-KEY": "your-api-key",
    "AUTHORIZATION": "Bearer token"
  }
}
```

## Responses

### 200

Successfully attached webhook to the specified senders.

Senders that could not be found are returned in the `failed` array with error details.

Content type: `application/json`. Schema: `AttachWebhookResult`.

- `webhook` — `object`
- `webhook.id` — `string`. Unique webhook identifier (starts with `wh_`).
- `webhook.name` — `string`. Name of the webhook.
- `webhook.url` — `string`. HTTPS endpoint URL where events are delivered.
- `webhook.secret` — `string`. Signing secret sent in the `PINNACLE-SIGNING-SECRET` header for request verification.
- `webhook.headers` — `object`, optional. Optional custom HTTP headers sent on every webhook delivery. Header names must match the regex `^[A-Za-z0-9][A-Za-z0-9_-]*$` — start with a letter or digit and contain only letters, digits, `-`, or `_`. Keys are case-insensitive and stored in uppercase. Values must be strings. The reserved `PINNACLE-SIGNING-SECRET` header is never returned here and cannot be overridden.
- `event` — `null | WebhookEventEnum`. The event type filter applied to these subscriptions.
- `event` Variant: variant 1
- `event` Variant: variant 2
- `senders` — `string[]`. Senders that were successfully attached (phone numbers in E.164 format or RCS agent IDs).
- `failed` — `object[]`. Senders that could not be attached, with error details.
- `failed[].sender` — `string`. The sender that failed.
- `failed[].error` — `string`. Reason for the failure.

Example:

```json
{
  "webhook": {
    "id": "wh_1234567890",
    "name": "SMS Delivery Tracker",
    "url": "https://api.myapp.com/webhooks/sms-delivery",
    "secret": "pss_1a2b3c4d5e6f7g8h9i0j",
    "headers": {
      "X-API-KEY": "sk_live_...",
      "X-TENANT-ID": "tenant_42"
    }
  },
  "event": "MESSAGE.STATUS",
  "senders": [
    "+14155551234",
    "agent_abc123"
  ],
  "failed": []
}
```

### 400

Validation failed. The payload has missing required fields and/or invalid types.

See https://zod.dev/error-formatting for more information.

Content type: `application/json`. Schema: `object`.

- `description` — `string`. Human-readable summary of validation failures.
- `errors` — `object`. Structured dictionary of issues containing two main sections:
- `errors`: Array of global validation errors not tied to specific fields
- `properties`: Object mapping field names to their specific validation errors, where each field contains an `errors` array

Example:

```json
{
  "description": "✖ Invalid input: expected string, received number\n  → at example_field",
  "errors": {
    "errors": [
      "Unrecognized key: 'extraKey'"
    ],
    "properties": {
      "example_field": {
        "errors": [
          "Invalid input: expected string, received NaN"
        ]
      }
    }
  }
}
```

### 401

The request lacks valid authentication credentials or the provided credentials are invalid.

Ensure you're including a valid API key in the request headers and that your account has the necessary permissions to access this endpoint.

Content type: `application/json`. Schema: `object`.

- `error` — `string`. Human-readable description of the error that occurred, corresponding to the HTTP status code.

Example:

```json
{
  "error": "Invalid API key."
}
```

### 404

The requested resource could not be found.

This may occur if the identifier is incorrect, the resource has been deleted, or you don't have permission to access it.

Content type: `application/json`. Schema: `object`.

- `error` — `string`. Human-readable description of the error that occurred, corresponding to the HTTP status code.

Example:

```json
{
  "error": "The requested resource could not be found."
}
```

### 500

An unexpected error occurred on Pinnacle's servers while processing your request.

If this error persists, please contact support with the request details.

Content type: `application/json`. Schema: `object`.

- `error` — `string`. Human-readable description of the error that occurred, corresponding to the HTTP status code.

Example:

```json
{
  "error": "An unexpected error occurred while processing your request."
}
```

## SDK examples

### TypeScript

```typescript
import { PinnacleClient } from "rcs-js";

const client = new PinnacleClient({
  apiKey: process.env.PINNACLE_API_KEY!,
});

await client.webhooks.attach({
    senders: ["+14155551234", "agent_abc123"]
});
```

### Python

```python
import os
from rcs import Pinnacle

client = Pinnacle(
    api_key=os.environ["PINNACLE_API_KEY"],
)

client.webhooks.attach(
    senders=["+14155551234", "agent_abc123"],
)
```

### Ruby

```ruby
require "rcs"

client = Pinnacle::Client.new(
  api_key: ENV.fetch("PINNACLE_API_KEY")
)

client.webhooks.attach(senders: %w[+14155551234 agent_abc123])
```

## Machine-readable schema

[OpenAPI 3.1 JSON](https://docs.pinnacle.sh/openapi.json)
