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

# 웹훅

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

BeatAPI 작업 완료 알림(선택 사항)을 받을 웹훅 엔드포인트를 만듭니다.

생성 시 반환된 서명 비밀 값을 저장하고, 모든 콜백을 원시 요청 본문으로 검증하며, 대체 기준 정보로 폴링을 계속하세요. 운영 환경의 재시도 동작은 [빠른 시작](/quick-guide#errors-and-retry-policy)을 참고하세요.

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

## Authentication

- `Authorization` header (bearer token, required) — API 키를 `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) — BeatAPI 작업 이벤트를 받는 공개 HTTPS 콜백 URL입니다. localhost나 사설 네트워크 URL은 사용하지 마세요.
- `description` (string, optional) — 계정에서 엔드포인트를 구분하기 위한 내부 라벨입니다(선택 사항).
- `events` (list of enum, optional) — 전달할 작업 이벤트입니다. 생략하면 `task.succeeded`와 `task.failed`를 모두 구독합니다.
  - Allowed values: `task.succeeded`, `task.failed`

## Response

### 201

웹훅 엔드포인트 생성됨

- `data` (WebhookEndpoint, required) — 생성되었거나 조회된 웹훅 엔드포인트입니다. 공개 API 응답은 생성 시 전체 서명 비밀 값을 반환하고 이후에는 가리며, 인증된 Dashboard 소유자는 명시적으로 다시 볼 수 있습니다.

## Errors

### 400 Bad Request Error

JSON 형식이 잘못되었거나, 이 엔드포인트가 받지 않는 필드·값·조합이 있거나(`bad_request`, 메시지에 해당 필드가 표시됨), 입력이 콘텐츠 검수에서 거부되었습니다(`content_policy_violation`). 그대로 재시도할 수 없습니다.

- `error` (ErrorError, required) — 구조화된 BeatAPI 오류입니다. 프로그램 로직에는 `code`(또는 `retryable`)를 사용하고, 지원 요청용으로 `request_id`를 보관하세요.

### 401 Unauthorized Error

API 키가 없거나(`missing_api_key`), 키가 잘못되었거나 만료, 비활성화, 소진되었습니다(`invalid_api_key`, 메시지에 원인이 표시됨). `Authorization: Bearer <key>` 형식으로 보내세요. 키는 `sk-` 접두사가 있어도 없어도 동작합니다. 재시도할 수 없습니다.

- `error` (ErrorError, required) — 구조화된 BeatAPI 오류입니다. 프로그램 로직에는 `code`(또는 `retryable`)를 사용하고, 지원 요청용으로 `request_id`를 보관하세요.

### 429 Too Many Requests Error

요청이 너무 많습니다(`rate_limit_exceeded`, 무료 모델 한도 포함). `Retry-After`(또는 `error.retry_after_seconds`)에 표시된 초만큼 기다린 뒤 재시도할 수 있습니다.

- `error` (ErrorError, required) — 구조화된 BeatAPI 오류입니다. 프로그램 로직에는 `code`(또는 `retryable`)를 사용하고, 지원 요청용으로 `request_id`를 보관하세요.

### 500 Internal Server Error

예상하지 못한 내부 오류입니다(`internal_error`). 재시도할 수 있으며, 문제가 계속되면 지원 요청 시 `request_id`를 함께 알려 주세요.

- `error` (ErrorError, required) — 구조화된 BeatAPI 오류입니다. 프로그램 로직에는 `code`(또는 `retryable`)를 사용하고, 지원 요청용으로 `request_id`를 보관하세요.

## Types

### WebhookEndpoint

- `id` (string, required) — 조회, 수정, 삭제 작업에 쓰는 안정적인 웹훅 엔드포인트 ID입니다.
- `object` (enum, required) — 객체 구분자이며 항상 `webhook_endpoint`입니다.
  - Allowed values: `webhook_endpoint`
- `url` (string, required) — 구독한 작업 이벤트를 받는 공개 HTTPS 콜백 URL입니다.
- `description` (string, required) — 계정에서 정의한 엔드포인트 라벨입니다.
- `events` (list of enum, required) — 이 엔드포인트로 전달되는 작업 이벤트 종류입니다.
  - Allowed values: `task.succeeded`, `task.failed`
- `status` (enum, required) — 전달 상태입니다. 비활성화된 엔드포인트는 이벤트를 받지 않습니다.
  - Allowed values: `active`, `disabled`
- `secret` (string, required) — 엔드포인트를 만들 때만 전체 값이 반환되며, 이후 응답에서는 가려진 값이 반환됩니다.
- `created_at` (integer, required) — 엔드포인트가 생성된 Unix 타임스탬프입니다.
- `updated_at` (integer, required) — 엔드포인트가 마지막으로 변경된 Unix 타임스탬프입니다.

### ErrorError

구조화된 BeatAPI 오류입니다. 프로그램 로직에는 `code`(또는 `retryable`)를 사용하고, 지원 요청용으로 `request_id`를 보관하세요.

- `code` (enum, required) — 기계가 읽을 수 있는 안정적인 오류 코드입니다. `unauthorized`는 401의 레거시 이름입니다. 2026-09-29부터 401은 `missing_api_key` 또는 `invalid_api_key`이지만 `unauthorized`가 여전히 나타날 수 있으며, `invalid_api_key`처럼 처리하세요. `result_expired`는 `operation: "result"`가 있는 `POST /v1/capabilities/run`에서만 반환됩니다.
  - 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) — 원인을 설명하는 영어 문장입니다. `bad_request`에서는 필드 이름을 명시합니다. 파싱용이 아니라 사람과 로그를 위한 문장입니다.
- `request_id` (string, required) — BeatAPI 지원 요청용으로 보관할 상관관계 ID입니다.
- `retryable` (boolean, optional) — 같은 호출을 다시 하면 성공할 수 있는지 여부입니다. 공개 표의 모든 코드에 있으며, 표에 없는 코드에서만 빠질 수 있습니다. rate_limit_exceeded, internal_error, processing_unavailable, processing_timeout, processing_failed, result_transfer_failed, realtime_capacity_unavailable은 true입니다. 요청, 키 또는 잔액을 먼저 바꿔야 하는 경우(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 및 기타 realtime 자격 증명 코드)는 false입니다.
- `retry_after_seconds` (integer, optional) — 재시도 전에 기다릴 초 수입니다. 429 `rate_limit_exceeded`에서만 전송되며, 같은 값이 `Retry-After` 헤더로도 전송됩니다. 다른 코드에는 없습니다.
- `docs` (string, optional) — 오류 대처 방법을 알려 주는 공개 Skill 페이지입니다. 기능 엔드포인트(`/v1/capabilities/search`, `/v1/capabilities/inspect`, `/v1/capabilities/run`)가 응답한 오류에만 있으며, 다른 엔드포인트에는 없습니다.

## 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 웹훅
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 웹훅
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 웹훅
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 웹훅
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 웹훅
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 웹훅
<?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 웹훅
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 웹훅
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()
```