> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# Webhooks

POST https://api.beatapi.io/v1/webhooks
Content-Type: application/json

Create a webhook endpoint for optional BeatAPI task completion callbacks.

Store the signing secret returned at creation, verify every callback against the raw request body, and keep polling as the fallback source of truth. See the [Quick Guide](/quick-guide#errors-and-retry-policy) for production retry behavior.

Reference: https://docs.beatapi.io/api-reference/webhooks

## Authentication

- `Authorization` header (bearer token, required) — Send your API key as `Authorization: Bearer <BEATAPI_API_KEY>`.

## Request

### Body (application/json)

This endpoint expects an object.

- `url` (string, required) — Public HTTPS callback URL that accepts BeatAPI task events. Do not use localhost or a private-network URL.
- `description` (string, optional) — Optional internal label for identifying the endpoint in your account.
- `events` (list of enum, optional) — Task events to deliver. Omit to subscribe to both `task.succeeded` and `task.failed`.
  - Allowed values: `task.succeeded`, `task.failed`

## Response

### 201

Webhook endpoint created

- `data` (WebhookEndpoint, required) — Created or retrieved webhook endpoint. Public API responses return the full signing secret at creation and mask it afterward; authenticated dashboard owners can explicitly reveal it again.

## Errors

### 400 Bad Request Error

Malformed JSON or a field, value or combination this endpoint does not accept (`bad_request`; the message names the field), or input refused by moderation (`content_policy_violation`). Not retryable unchanged.

- `error` (ErrorError, required) — Structured BeatAPI error. Use `code` (or `retryable`) for program logic and retain `request_id` for support.

### 401 Unauthorized Error

No API key (`missing_api_key`), or a key that is wrong, expired, disabled or exhausted (`invalid_api_key`; the message says which). Send it as `Authorization: Bearer <key>`; the key works with or without the `sk-` prefix. Not retryable.

- `error` (ErrorError, required) — Structured BeatAPI error. Use `code` (or `retryable`) for program logic and retain `request_id` for support.

### 429 Too Many Requests Error

Too many requests (`rate_limit_exceeded`, including the free-model limits) or too many tasks processing at once (`user_concurrency_exceeded`). Retryable after `Retry-After` seconds (also `error.retry_after_seconds`).

- `error` (ErrorError, required) — Structured BeatAPI error. Use `code` (or `retryable`) for program logic and retain `request_id` for support.

### 500 Internal Server Error

Unexpected internal error (`internal_error`). Retryable; keep `request_id` for support if it persists.

- `error` (ErrorError, required) — Structured BeatAPI error. Use `code` (or `retryable`) for program logic and retain `request_id` for support.

## Types

### WebhookEndpoint

- `id` (string, required) — Stable webhook endpoint ID used for get, update, and delete operations.
- `object` (enum, required) — Object discriminator; always `webhook_endpoint`.
  - Allowed values: `webhook_endpoint`
- `url` (string, required) — Public HTTPS callback URL receiving subscribed task events.
- `description` (string, required) — Account-defined label for the endpoint.
- `events` (list of enum, required) — Task event types delivered to this endpoint.
  - Allowed values: `task.succeeded`, `task.failed`
- `status` (enum, required) — Delivery status. Disabled endpoints do not receive events.
  - Allowed values: `active`, `disabled`
- `secret` (string, required) — Returned in full only when the endpoint is created. Later responses return a masked value.
- `created_at` (integer, required) — Unix timestamp when the endpoint was created.
- `updated_at` (integer, required) — Unix timestamp when the endpoint last changed.

### ErrorError

Structured BeatAPI error. Use `code` (or `retryable`) for program logic and retain `request_id` for support.

- `code` (enum, required) — Stable machine-readable error code. `unauthorized` is the deprecated name for a 401; since 2026-09-29 a 401 is `missing_api_key` or `invalid_api_key`. Treat a legacy `unauthorized` like `invalid_api_key`.
  - Allowed values: `bad_request`, `missing_api_key`, `invalid_api_key`, `unauthorized`, `forbidden`, `not_found`, `insufficient_credits`, `idempotency_conflict`, `user_concurrency_exceeded`, `rate_limit_exceeded`, `content_policy_violation`, `processing_unavailable`, `processing_failed`, `processing_timeout`, `result_transfer_failed`, `invalid_signature`, `realtime_disabled`, `realtime_capacity_unavailable`, `realtime_session_expired`, `origin_not_allowed`, `invalid_client_secret`, `transport_not_allowed`, `internal_error`
- `message` (string, required) — English sentence that states the cause; for `bad_request` it names the field. Written for people and logs, not for parsing.
- `retryable` (boolean, required) — Whether making the same call again could succeed. Present on every error; branch on it instead of the status code. True for 429, 500 and 503 (rate_limit_exceeded, user_concurrency_exceeded, internal_error, processing_unavailable, processing_timeout, processing_failed, result_transfer_failed, realtime_capacity_unavailable). False for a request, key or balance that must change first (bad_request, content_policy_violation, missing_api_key, invalid_api_key, insufficient_credits, forbidden, not_found, idempotency_conflict, realtime_disabled and the other realtime credential codes).
- `request_id` (string, required) — Correlation ID to retain for BeatAPI support.
- `retry_after_seconds` (integer, optional) — Seconds to wait before retrying. Present on every 429, and on a 503 when the wait is known; the same value is sent in the `Retry-After` header.
- `docs` (string, optional) — The public Skill page that says what to do about an error. Present on errors answered by the capability endpoints (`/v1/capabilities/search`, `/v1/capabilities/inspect`, `/v1/capabilities/run`); absent on every other endpoint.

## Examples

**Request**

```json
{
  "url": "https://example.com/beatapi-webhook",
  "events": [
    "task.succeeded",
    "task.failed"
  ]
}
```

**Response**

```json
{
  "data": {
    "id": "wh_9aBcD",
    "object": "webhook_endpoint",
    "url": "https://example.com/beatapi-webhook",
    "description": "Production webhook",
    "events": [
      "task.succeeded",
      "task.failed"
    ],
    "status": "active",
    "secret": "whsec_example_store_this_once",
    "created_at": 1782210000,
    "updated_at": 1782210000
  }
}
```

**SDK Code**

```python Webhooks
import requests

url = "https://api.beatapi.io/v1/webhooks"

payload = {
    "url": "https://example.com/beatapi-webhook",
    "events": ["task.succeeded", "task.failed"]
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Webhooks
const url = 'https://api.beatapi.io/v1/webhooks';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"url":"https://example.com/beatapi-webhook","events":["task.succeeded","task.failed"]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Webhooks
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.beatapi.io/v1/webhooks"

	payload := strings.NewReader("{\n  \"url\": \"https://example.com/beatapi-webhook\",\n  \"events\": [\n    \"task.succeeded\",\n    \"task.failed\"\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	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 Webhooks
require 'uri'
require 'net/http'

url = URI("https://api.beatapi.io/v1/webhooks")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"url\": \"https://example.com/beatapi-webhook\",\n  \"events\": [\n    \"task.succeeded\",\n    \"task.failed\"\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java Webhooks
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.beatapi.io/v1/webhooks")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"url\": \"https://example.com/beatapi-webhook\",\n  \"events\": [\n    \"task.succeeded\",\n    \"task.failed\"\n  ]\n}")
  .asString();
```

```php Webhooks
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.beatapi.io/v1/webhooks', [
  'body' => '{
  "url": "https://example.com/beatapi-webhook",
  "events": [
    "task.succeeded",
    "task.failed"
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Webhooks
using RestSharp;

var client = new RestClient("https://api.beatapi.io/v1/webhooks");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"url\": \"https://example.com/beatapi-webhook\",\n  \"events\": [\n    \"task.succeeded\",\n    \"task.failed\"\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Webhooks
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "url": "https://example.com/beatapi-webhook",
  "events": ["task.succeeded", "task.failed"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.beatapi.io/v1/webhooks")! 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()
```