> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Webhooks

## 개요

ElevenLabs 내의 특정 이벤트는 webhook을 트리거하도록 구성할 수 있으므로, 외부 애플리케이션과 시스템에서 발생 시점에 이러한 이벤트를 수신하고 처리할 수 있습니다. 현재 지원되는 이벤트 유형은 다음과 같습니다.

| 이벤트 유형                           | 설명                                                                                              |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| `post_call_transcription`        | Agents Platform 통화가 종료되고 분석이 완료됨                                                                |
| `voice_removal_notice`           | 공유 음성의 삭제가 예정됨                                                                                  |
| `voice_removal_notice_withdrawn` | 공유 음성의 삭제 예정이 취소됨                                                                               |
| `voice_removed`                  | 공유 음성이 삭제되어 더 이상 사용할 수 없음                                                                       |
| `flows_generation`               | [이미지 & 비디오 생성](/docs/ko/eleven-api/guides/how-to/image-and-video/webhooks)이 완료되었거나 실패함          |
| `flows_template_run`             | [템플릿 실행](/docs/ko/eleven-api/guides/how-to/templates/run-a-template#get-the-result)이 완료되었거나 실패함 |

## 구성

webhook은 일반 설정 페이지에서 만들고, 비활성화하고, 삭제할 수 있습니다. [워크스페이스](/docs/ko/overview/administration/workspaces/overview)의 사용자는 워크스페이스 관리자만 해당 워크스페이스의 webhook을 구성할 수 있습니다.

![HMAC webhook 구성](/docs/_fern-files/_img/9ea298daac1c64eb43c802a12f7824e83accca44ba2edd1d01a39bcd62c0b9d6.webp)

생성 후에는 [Agents Platform](/docs/ko/eleven-agents/workflows/post-call-webhooks)과 같은 제품 설정에서 이벤트를 수신할 webhook을 선택할 수 있습니다.

`flows_generation` 및 `flows_template_run` 이벤트는 하나의 구독으로 모두 활성화됩니다. **수신할 이벤트 선택**에서 **이미지 & 비디오 API 생성 또는 템플릿 실행 완료**를 선택하거나, [워크스페이스 webhook 업데이트](/docs/ko/api-reference/webhooks/update)에 `flows` 이벤트를 전달하세요. 생성 또는 실행의 생성 요청에 `webhook` 필드가 포함된 경우에만 이벤트가 전송됩니다.

webhook은 언제든 일반 설정 페이지에서 비활성화할 수 있습니다. 연속 실패가 10회 이상이고 마지막 성공 전달이 7일보다 오래되었거나 성공적으로 전달된 적이 없는 webhook은 반복적으로 실패하면 자동으로 비활성화됩니다. 자동 비활성화된 webhook은 설정 페이지에서 다시 활성화해야 합니다. 어떤 제품에서도 사용하지 않는 webhook은 삭제할 수 있습니다.

## 재시도

webhook별로 재시도를 활성화하면 요청이 실패했을 때 전달을 자동으로 다시 시도할 수 있습니다. 재시도는 기본적으로 비활성화되어 있습니다. [API](/docs/ko/api-reference/webhooks/create) 또는 webhook 설정에서 webhook을 만들거나 업데이트할 때 재시도를 활성화하세요.

> **Note**
>
> 재시도는 전사(`post_call_transcription`), 오디오(`post_call_audio`), 통화 시작 실패
> (`call_initiation_failure`) 이벤트를 포함한 ElevenAgents [통화 후 webhook](/docs/ko/eleven-agents/workflows/post-call-webhooks)에서 지원됩니다.

### 재시도 일정

전달 시도가 재시도 가능한 오류로 실패하면 시스템은 시도 사이의 지연 시간을 늘리며 최대 5회까지 재시도합니다.

| 시도 | 지연 시간 |
| -- | ----- |
| 1  | 즉시    |
| 2  | 30초   |
| 3  | 2분    |
| 4  | 8분    |
| 5  | 30분   |

부하를 분산하고 동시 요청 폭주 문제를 방지하기 위해 각 재시도에는 지연 시간의 최대 10%에 해당하는 작은 무작위 지터가 추가됩니다.

### 재시도 가능한 오류

모든 실패가 재시도를 트리거하는 것은 아닙니다. 다음 실패만 재시도 가능한 것으로 간주됩니다.

* `5xx` 상태 코드(500, 502, 503, 504 등의 서버 오류)
* `429`(요청 과다)
* `408`(요청 시간 초과)
* 연결 오류 및 요청 시간 초과

400, 401, 403, 404와 같은 `4xx` 범위의 요청 오류는 일반적으로 수동 수정이 필요한 구성 문제를 나타내므로 재시도하지 **않습니다**.

### webhook별 큐 제한

각 webhook은 대기 중인 재시도 작업이 100개로 제한됩니다. webhook에 대기 중인 재시도가 100개를 초과하면 기존 재시도가 처리될 때까지 추가 작업이 삭제됩니다. 이는 잘못 구성된 webhook 하나가 과도한 리소스를 소비하는 것을 방지합니다.

오디오 webhook에는 두 가지 추가 제한이 있습니다. 50MiB보다 큰 오디오 페이로드는 한 번만 전달되며 재시도되지 않습니다. 또한 단일 webhook의 대기 중인 오디오 재시도는 총 400MiB를 초과할 수 없습니다.

### 자동 비활성화 동작

시스템은 각 webhook의 연속 전달 실패를 추적합니다. 다음 두 조건을 모두 충족하면 webhook이 자동으로 비활성화됩니다.

* 연속 전달 실패가 10회 이상 발생했습니다.
* webhook이 성공적으로 전달된 적이 없거나, 마지막 성공 전달이 7일보다 오래되었습니다.

webhook이 자동으로 비활성화되면 워크스페이스 관리자에게 이메일 알림이 전송됩니다. 전달을 재개하려면 설정 페이지에서 webhook을 수동으로 다시 활성화해야 합니다.

## 통합

webhook과 통합하려면 POST 요청으로 webhook 이벤트 데이터를 수신하는 엔드포인트 핸들러를 만드세요. 서명을 검증한 후 핸들러는 성공적인 수신을 나타내기 위해 즉시 HTTP 200을 반환해야 합니다. 성공 응답을 반복해서 반환하지 못하면 webhook이 자동으로 비활성화될 수 있습니다.

재시도 페이로드는 원래 전달 시도와 동일합니다. webhook 소비자는 페이로드만으로 최초 전달과 재시도를 구분할 수 없으므로, 핸들러를 **멱등적**으로 설계해야 합니다. 즉, 같은 이벤트를 여러 번 처리해도 동일한 결과가 나와야 합니다. 필요한 경우 `event_timestamp`와 `conversation_id` 등의 이벤트별 식별자를 사용하여 이벤트 중복을 제거하세요.

### 최상위 필드

| 필드                | 유형     | 설명        |
| ----------------- | ------ | --------- |
| `type`            | string | 이벤트 유형    |
| `data`            | object | 이벤트 데이터   |
| `event_timestamp` | string | 이벤트 발생 시점 |

## webhook 페이로드 예시

```json
{
  "type": "post_call_transcription",
  "event_timestamp": 1739537297,
  "data": {
    "agent_id": "xyz",
    "conversation_id": "abc",
    "status": "done",
    "transcript": [
      {
        "role": "agent",
        "message": "Hey there angelo. How are you?",
        "tool_calls": null,
        "tool_results": null,
        "feedback": null,
        "time_in_call_secs": 0,
        "conversation_turn_metrics": null
      },
      {
        "role": "user",
        "message": "Hey, can you tell me, like, a fun fact about 11 Labs?",
        "tool_calls": null,
        "tool_results": null,
        "feedback": null,
        "time_in_call_secs": 2,
        "conversation_turn_metrics": null
      },
      {
        "role": "agent",
        "message": "I do not have access to fun facts about Eleven Labs. However, I can share some general information about the company. Eleven Labs is an AI voice technology platform that specializes in voice cloning and text-to-speech...",
        "tool_calls": null,
        "tool_results": null,
        "feedback": null,
        "time_in_call_secs": 9,
        "conversation_turn_metrics": {
          "convai_llm_service_ttfb": {
            "elapsed_time": 0.3704247010173276
          },
          "convai_llm_service_ttf_sentence": {
            "elapsed_time": 0.5551181449554861
          }
        }
      }
    ],
    "metadata": {
      "start_time_unix_secs": 1739537297,
      "call_duration_secs": 22,
      "cost": 296,
      "deletion_settings": {
        "deletion_time_unix_secs": 1802609320,
        "deleted_logs_at_time_unix_secs": null,
        "deleted_audio_at_time_unix_secs": null,
        "deleted_transcript_at_time_unix_secs": null,
        "delete_transcript_and_pii": true,
        "delete_audio": true
      },
      "feedback": {
        "overall_score": null,
        "likes": 0,
        "dislikes": 0
      },
      "authorization_method": "authorization_header",
      "charging": {
        "dev_discount": true
      },
      "termination_reason": ""
    },
    "analysis": {
      "evaluation_criteria_results": {},
      "data_collection_results": {},
      "call_successful": "success",
      "transcript_summary": "The conversation begins with the agent asking how Angelo is, but Angelo redirects the conversation by requesting a fun fact about 11 Labs. The agent acknowledges they don't have specific fun facts about Eleven Labs but offers to provide general information about the company. They briefly describe Eleven Labs as an AI voice technology platform specializing in voice cloning and text-to-speech technology. The conversation is brief and informational, with the agent adapting to the user's request despite not having the exact information asked for."
    },
    "conversation_initiation_client_data": {
      "conversation_config_override": {
        "agent": {
          "prompt": null,
          "first_message": null,
          "language": "en"
        },
        "tts": {
          "voice_id": null
        }
      },
      "custom_llm_extra_body": {},
      "dynamic_variables": {
        "user_name": "angelo"
      }
    }
  }
}
```

## 인증

수신 측에서는 들어오는 모든 웹훅의 유효성을 검증하는 것이 중요합니다. 현재 웹훅은 HMAC 서명을 통한 인증을 지원합니다. 다음 방법으로 HMAC 인증을 설정하세요.

* 웹훅 생성 시 생성되는 공유 시크릿을 안전하게 저장합니다.
* SDK를 사용하여 엔드포인트에서 ElevenLabs-Signature 헤더를 검증합니다.

JavaScript SDK는 `constructEvent`를 제공하며, Python SDK는 **`rawBody`**, **`sig_header`**, \*\*`secret`\*\*을 사용하는 `construct_event`를 제공합니다(Python에서는 `payload` / `signature`이라는 이름을 사용하지 않습니다). 두 SDK 모두 서명을 검증하고 타임스탬프를 확인하며 JSON 페이로드를 파싱합니다.

#### Python

FastAPI를 사용하는 웹훅 핸들러 예시:

```python
from dotenv import load_dotenv
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from elevenlabs.client import ElevenLabs
from elevenlabs.errors import BadRequestError
import os

load_dotenv()

app = FastAPI()
elevenlabs = ElevenLabs(
    api_key=os.getenv("ELEVENLABS_API_KEY"),
)

WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")

@app.post("/webhook")
async def receive_message(request: Request):
    payload = await request.body()
    signature = request.headers.get("elevenlabs-signature")

    try:
        event = elevenlabs.webhooks.construct_event(
            rawBody=payload.decode("utf-8"),
            sig_header=signature,
            secret=WEBHOOK_SECRET,
        )
    except BadRequestError as e:
        return JSONResponse(content={"error": "Invalid signature"}, status_code=401)

    # construct_event returns a dict (parsed JSON), not an object with attributes
    if event.get("type") == "post_call_transcription":
        print(f"Received transcription: {event.get('data')}")

    return {"status": "received"}
```

#### JavaScript

#### Express

Express를 사용하는 웹훅 핸들러 예시:

```javascript
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';
import express from 'express';

const app = express();

const elevenlabs = new ElevenLabsClient();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Use express.text() to preserve raw body for signature verification
app.post('/webhook', express.text({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['elevenlabs-signature'];
  const payload = req.body; // Raw string body

  let event;
  try {
    event = await elevenlabs.webhooks.constructEvent(payload, signature, WEBHOOK_SECRET);
  } catch (error) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the webhook event
  if (event.type === 'post_call_transcription') {
    console.log('Received transcription:', event.data);
  }

  res.status(200).json({ received: true });
});
```

#### Next.js

Next.js API 라우트를 사용하는 웹훅 핸들러 예시:

**`app/api/webhook/route.ts`**

```typescript app/api/webhook/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';

const elevenlabs = new ElevenLabsClient();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get('elevenlabs-signature');

  let event;
  try {
    event = await elevenlabs.webhooks.constructEvent(body, signature, WEBHOOK_SECRET);
  } catch (error) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  // Process the webhook event
  if (event.type === 'post_call_transcription') {
    console.log('Received transcription:', event.data);
  }

  return NextResponse.json({ received: true }, { status: 200 });
}
```