> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.pinnacle.sh/v-2/api-reference/webhooks/attach-webhook/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pinnacle.sh/_mcp/server. # Attach Webhook POST https://api.pinnacle.sh/webhooks/attach Content-Type: application/json 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. Reference: https://docs.pinnacle.sh/api-reference/webhooks/attach-webhook ## Authentication - `PINNACLE-API-KEY` header (required) — API Key authentication via header ## Request ### Body (application/json) This endpoint expects an AttachWebhookRequest. - `senders` (list of string, required) — 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](https://app.pinnacle.sh/dashboard/development/webhooks). Supplying `headers` alongside `webhookId` **overwrites** the stored headers on the webhook. Omit `headers` to leave them unchanged. - `name` (string, optional) — Name for a new webhook (required if no `webhookId`). - `url` (string, optional) — HTTPS endpoint URL for a new webhook (required if no `webhookId`). - `event` (enum, optional, nullable) — 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`. - Allowed values: `MESSAGE.STATUS`, `MESSAGE.RECEIVED`, `USER.TYPING`, `FORM.SUBMISSION`, `CAMPAIGN.STATUS`, `FAX.STATUS`, `FAX.RECEIVED` - `headers` (map from string to string, optional, nullable) — 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](https://datatracker.ietf.org/doc/html/rfc9110#name-field-names) 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. ## Response ### 200 Successfully attached webhook to the specified senders. Senders that could not be found are returned in the `failed` array with error details. - `webhook` (AttachWebhookResponseWebhook, required) - `event` (enum, required, nullable) — The event type filter applied to these subscriptions. - Allowed values: `MESSAGE.STATUS`, `MESSAGE.RECEIVED`, `USER.TYPING`, `FORM.SUBMISSION`, `CAMPAIGN.STATUS`, `FAX.STATUS`, `FAX.RECEIVED` - `senders` (list of string, required) — Senders that were successfully attached (phone numbers in E.164 format or RCS agent IDs). - `failed` (list of FailedSender, required) — Senders that could not be attached, with error details. ## Errors ### 400 Bad Request Error Validation failed. The payload has missing required fields and/or invalid types. See [https://zod.dev/error-formatting](https://zod.dev/error-formatting) for more information. - `description` (string, required) — Human-readable summary of validation failures. - `errors` (ZodErrorErrors, required) — 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 ### 401 Unauthorized Error 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. - `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code. ### 404 Not Found Error 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. - `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code. ### 500 Internal Server Error An unexpected error occurred on Pinnacle's servers while processing your request. If this error persists, please contact support with the request details. - `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code. ## Types ### AttachWebhookResponseWebhook - `id` (string, required) — Unique webhook identifier (starts with `wh_`). - `name` (string, required) — Name of the webhook. - `url` (string, required) — HTTPS endpoint URL where events are delivered. - `secret` (string, required) — Signing secret sent in the `PINNACLE-SIGNING-SECRET` header for request verification. - `headers` (map from string to string, optional, nullable) — 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. ### FailedSender - `sender` (string, required) — The sender that failed. - `error` (string, required) — Reason for the failure. ### ZodErrorErrors 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 ## Examples **Request** ```json { "senders": [ "+14155551234", "agent_abc123" ] } ``` **Response** ```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": [] } ``` **SDK Code** ```python Attached Webhook import requests url = "https://api.pinnacle.sh/webhooks/attach" payload = { "senders": ["+14155551234", "agent_abc123"] } headers = { "PINNACLE-API-KEY": "", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` ```javascript Attached Webhook const url = 'https://api.pinnacle.sh/webhooks/attach'; const options = { method: 'POST', headers: {'PINNACLE-API-KEY': '', 'Content-Type': 'application/json'}, body: '{"senders":["+14155551234","agent_abc123"]}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go Attached Webhook package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.pinnacle.sh/webhooks/attach" payload := strings.NewReader("{\n \"senders\": [\n \"+14155551234\",\n \"agent_abc123\"\n ]\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("PINNACLE-API-KEY", "") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby Attached Webhook require 'uri' require 'net/http' url = URI("https://api.pinnacle.sh/webhooks/attach") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["PINNACLE-API-KEY"] = '' request["Content-Type"] = 'application/json' request.body = "{\n \"senders\": [\n \"+14155551234\",\n \"agent_abc123\"\n ]\n}" response = http.request(request) puts response.read_body ``` ```java Attached Webhook import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.pinnacle.sh/webhooks/attach") .header("PINNACLE-API-KEY", "") .header("Content-Type", "application/json") .body("{\n \"senders\": [\n \"+14155551234\",\n \"agent_abc123\"\n ]\n}") .asString(); ``` ```php Attached Webhook request('POST', 'https://api.pinnacle.sh/webhooks/attach', [ 'body' => '{ "senders": [ "+14155551234", "agent_abc123" ] }', 'headers' => [ 'Content-Type' => 'application/json', 'PINNACLE-API-KEY' => '', ], ]); echo $response->getBody(); ``` ```csharp Attached Webhook using RestSharp; var client = new RestClient("https://api.pinnacle.sh/webhooks/attach"); var request = new RestRequest(Method.POST); request.AddHeader("PINNACLE-API-KEY", ""); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"senders\": [\n \"+14155551234\",\n \"agent_abc123\"\n ]\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift Attached Webhook import Foundation let headers = [ "PINNACLE-API-KEY": "", "Content-Type": "application/json" ] let parameters = ["senders": ["+14155551234", "agent_abc123"]] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.pinnacle.sh/webhooks/attach")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` > One API for RCS, iMessage, MMS, and SMS. Build, test, and scale every channel — send your first message in minutes, not weeks.