> 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>`.

## Servers

- `https://api.beatapi.io` (Direct, default)
- `https://api.beatapi.io` (API)

## 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). 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 legacy name for a 401; since 2026-09-29 a 401 is `missing_api_key` or `invalid_api_key`, but `unauthorized` may still appear. Treat it like `invalid_api_key`. `result_expired` is returned only by `POST /v1/capabilities/run` with `operation: "result"`.
  - Allowed values: `bad_request`, `missing_api_key`, `invalid_api_key`, `unauthorized`, `forbidden`, `not_found`, `insufficient_credits`, `idempotency_conflict`, `rate_limit_exceeded`, `content_policy_violation`, `processing_unavailable`, `processing_failed`, `processing_timeout`, `result_transfer_failed`, `result_expired`, `not_implemented`, `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.
- `request_id` (string, required) — Correlation ID to retain for BeatAPI support.
- `retryable` (boolean, optional) — Whether making the same call again could succeed. Present for every code in the public table; it may be absent only for a code outside the table. True for rate_limit_exceeded, internal_error, processing_unavailable, processing_timeout, processing_failed, result_transfer_failed and 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, unauthorized, insufficient_credits, forbidden, not_found, idempotency_conflict, result_expired, not_implemented, realtime_disabled and the other realtime credential codes).
- `retry_after_seconds` (integer, optional) — Seconds to wait before retrying. Sent only with a 429 `rate_limit_exceeded`, and the same value is sent in the `Retry-After` header. No other code carries it.
- `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()
```