전화·SIP

전화·SIP (Telephony and SIP)

전화를 GPT-Live와 Realtime API에 연결하는 방법을 다뤄요. SIP 트렁크를 통하거나 오디오를 중계하는 앱을 통해서요.

출처: 문서

본문

전화 연결 방식 고르기

전화 통화는 SIP 트렁크를 통하거나 오디오를 중계하는 애플리케이션을 통해 GPT-Live에 도달할 수 있어요. 기존 전화 시스템과 앱이 오디오를 처리해야 하는 위치에 맞는 경로를 선택하세요.

연결 오디오와 애플리케이션 역할
Direct SIP 제공자가 통화 오디오를 OpenAI와 교환해요. 애플리케이션은 웹훅, 세션 구성, 통화 결정, 비즈니스 로직을 담당해요.
Server audio bridge 애플리케이션이 제공자 또는 방 오디오를 WebSocket으로 GPT-Live에 중계해요. 두 연결, 이벤트 변환, 재생, 통화 수명 주기를 관리해요.

제공자와 애플리케이션의 연결, 그리고 애플리케이션과 OpenAI의 연결은 별개예요. 예를 들어 호출자가 SIP로 방에 들어오고, 그 방의 에이전트가 WebSocket으로 GPT-Live에 연결할 수 있어요. Twilio, Telnyx, LiveKit, Daily/Pipecat를 쓴다면 GPT-Live partner integrations에서 제공자별 가이드를 참고하세요.

Direct SIP

Direct SIP는 통화 오디오를 제공자-OpenAI 미디어 경로에 유지해요. SIP 시그널링은 TLS를 쓰고, GPT-Live는 통화 오디오에 SRTP가 필요해요. 백엔드는 여전히 인입 통화 결정, 세션 구성, 인증, 비즈니스 로직을 소유해요. 백엔드가 세션 이벤트를 받거나 명령을 보내야 한다면 사이드밴드 연결을 써요. SIP가 오디오를 나르는 동안 기존 대화에 붙어요. 각 동작에 핸들러 하나를 지정해, 중복 웹훅 전달이나 여러 연결에서 관찰된 이벤트가 도구를 두 번 실행하지 않게 해요.

인바운드 통화 처리

이 흐름을 쓰기 전에 프로젝트에 GPT-Live SIP 지원이 활성화되어 있고 제공자의 SIP 트렁크가 그 프로젝트로 라우팅되는지 확인하세요.

들어오는 통화 받기

프로젝트의 웹훅 엔드포인트를 live.transport.incoming으로 구성해요. 웹훅 서명을 검증하고 전달을 중복 제거한 뒤 통화를 수락하거나 거절해요. 웹훅은 SIP 통화를 data.type: "sip"으로 식별하고 data.session_id를 제공해요. 그 세션 ID를 모든 Live 통화 작업에 그대로 사용해요. data.sip_headers는 권한이 아니라 신뢰할 수 없는 호출자 메타데이터로 취급해요.

기존 통합에서는 data.type이 없는 이전 live.call.incoming 이벤트를 계속 받을 수도 있어요. 마이그레이션 중에는 두 이름을 모두 처리하고, 레거시 전달·재시도가 소진될 때까지 옛 구독을 유지하세요. 같은 대기 통화가 Realtime 웹훅도 낼 수 있으므로, 두 API로 모두 수락하기보다 accept/reject 결정에 핸들러 하나를 지정해요.

통화 수락 또는 거절

애플리케이션의 인증·라우팅 규칙을 적용해요. 통화를 수락하려면 최상위 session 객체와 함께 인증된 POST /v1/live/sessions/{session_id}/accept 요청을 보내요:

{
  "session": {
    "type": "live",
    "model": "gpt-live-1",
    "instructions": "You are answering an inbound support call.",
    "audio": { "output": { "voice": "marin" } },
    "delegation": { "type": "client" }
  }
}

통화 제어 요청에는 신뢰하는 백엔드의 Authorization: Bearer ***를 써요. 음성과 위임 모드는 수락 시에 고르세요. SIP가 오디오 형식을 협상하므로 audio.format을 생략해요. 예시는 클라이언트 위임을 선택하는데, 백엔드가 위임 작업을 처리해야 해요. 클라이언트·Responses 구성은 Delegation and tools을 참고하세요.

성공적인 수락은 세션 초기화 후 빈 바디와 함께 200 OK를 반환해요. 통화를 수락으로 처리하기 전에 HTTP 오류를 처리하세요. 통화 거절은 POST /v1/live/sessions/{session_id}/reject에 SIP 상태를 담아 보내요(예: 부재중은 { "status_code": 486 }). 상태는 300~699 사이 정수여야 해요. 첫 accept/reject 결정이 이기고, 나중에 오는 경쟁 결정은 decision_already_made를 반환해요.

백엔드 연결

수락 후 wss://api.openai.com/v1/live/sessions/{session_id}/attach에서 사이드밴드 WebSocket을 연결해요. 수락된 세션 ID와 같은 프로젝트 인증·연결 헤더를 써요. session.start를 다시 보내지 마세요. SIP가 통화 오디오를 나르고, 사이드밴드는 전사·위임·도구·명령·반사 오디오에 써요. 여러 연결이 이벤트를 관찰해도 각 부수 효과에 소유자 하나를 정하세요.

키패드 이벤트 관찰

사이드밴드는 호출자가 키를 누르면 transport.dtmf.received를, 호스팅 도구가 성공적으로 톤을 보낸 뒤 transport.dtmf.send를 받아요. 둘 다 알림일 뿐이에요. event 필드는 09, *, #, 또는 AD 중 하나를 담아요.

통화 전환 또는 종료

통화 전환은 POST /v1/live/sessions/{session_id}/refer에 { "target_uri": "sip:[email protected]" } 같은 목적지를 보내요. 전화 끊기는 요청 바디 없이 POST /v1/live/sessions/{session_id}/hangup을 보내요. 둘 다 성공 시 빈 바디와 200 OK를 반환해요. session.closed가 최종 사용량을 줄 때까지 사이드밴드를 열어 두었다가 애플리케이션 리소스를 해제해요. 연결이 먼저 끊기면 종료를 불완전으로 기록해요.

아웃바운드 통화 걸기

Create session으로 SIP 제공자를 통해 전화번호로 걸어요. GPT-Live가 대화를 나르는 동안 제공자가 전화 네트워크 연결을 처리해요. 아웃바운드 SIP 통화는 조직에서 활성화되어야 하고, Realtime API 통화 생성 끝점이 아니라 Live API로 제공돼요.

트렁크 구성

TLS 시그널링, Opus 오디오, SDES-SRTP 미디어를 지원하는 트렁크를 쓰세요. 통화를 걸기 전에 제공자 설정에서 Opus와 SRTP를 켜요. 각 요청에 트렁크 구성을 제공해요.

필드 값
transport.destination 걸 전화번호, E.164 형식(예: +141****0123). SIP URI 목적지는 지원되지 않아요.
transport.trunk.provider_url sips:sip.example.com:5061 같은 제공자 엔드포인트. 기본 포트는 5061, ;transport=tcp는 선택.
transport.trunk.auth.type SIP Digest 인증에는 digest.
transport.trunk.auth.username 제공자의 SIP 사용자 이름.
transport.trunk.auth.password 제공자의 SIP 비밀번호.
transport.trunk.caller_number 제공자에게 보낼 발신자 전화번호, E.164 형식.

제공자 엔드포인트는 TLS 시그널링을 위해 sips:를 써야 해요. URL에 자격증명, 경로, URI 헤더, 다른 URI 파라미터를 넣지 마세요. 로컬 호스트 이름과 사설·로컬 IP 주소는 거부돼요. OpenAI API 키와 SIP 자격증명은 서버에 보관하세요.

세션 만들기

세션 구성과 transport.type: "sip"으로 POST /v1/live/sessions을 보내요. 세션을 만들 때 음성과 위임 모드를 고르고, SIP가 오디오 형식을 협상하므로 audio.format을 생략해요. 이 예시는 curl과 jq를 써요. 서버 환경에 OPENAI_API_KEY, SIP_USERNAME, SIP_PASSWORD를 설정하고 예시 제공자 엔드포인트와 전화번호를 실제 값으로 바꾸세요. 예시는 클라이언트 위임을 선택하므로 백엔드가 위임 작업을 처리해야 해요.

jq -n \
  --arg username "$SIP_USERNAME" \
  --arg password "$SIP_PASSWORD" \
  '{
    "session": {
      "model": "gpt-live-1",
      "instructions": "Help the user schedule an appointment.",
      "audio": { "output": { "voice": "marin" } },
      "delegation": { "type": "client" }
    },
    "transport": {
      "type": "sip",
      "destination": "+141****0123",
      "trunk": {
        "provider_url": "sips:sip.example.com:5061",
        "auth": {
          "type": "digest",
          "username": $username,
          "password": $password
        },
        "caller_number": "+141****0100"
      }
    }
  }' | curl https://api.openai.com/v1/live/sessions \
    -H "Authorization: Bearer ***" \
    -H "Content-Type: application/json" \
    --data-binary @-

세션이 초기화된 후 요청이 200 OK를 반환해요.

{
  "session": { "id": "live_123" },
  "transport": { "type": "sip" }
}

이 응답이 통화가 수락됐다는 뜻은 아니에요. SDP나 트렁크 자격증명이 들어 있지 않아요. 사이드밴드 연결과 통화 제어에 session.id를 그대로 보존해요. 아웃바운드 통화에는 인입 통화 웹훅이나 accept 요청이 필요 없어요.

통화 모니터링과 종료

OpenAI API 키로 wss://api.openai.com/v1/live/sessions/{session_id}/attach에 백엔드를 연결해요. session.start를 다시 보내지 마세요. 사이드밴드 연결이 대화 이벤트·위임 작업·통화 진행을 나르고, SIP는 오디오를 나릅니다.

이벤트 의미
transport.ringing 제공자가 링잉 또는 early media를 보고해요.
transport.answered 통화가 수락되고 미디어가 설정됐어요.
transport.failed 세션 초기화 후 통화 설정이 실패했어요. error.code와 error.message를 확인하세요.

각 통화 진행 이벤트에는 event_id와 session_id가 포함돼요. 생성 직후 연결하세요. 사이드밴드는 지난 3초의 이벤트만 재생하므로, 늦게 붙이면 이른 통화 진행을 놓칠 수 있어요. 재생 이벤트는 원래 event ID를 유지해요. event_id로 이벤트를 중복 제거하세요. 인바운드와 같은 전환·전화 끊기 동작을 쓰고, Usage and graceful close의 설명대로 session.closed와 최종 사용량을 위해 사이드밴드를 열어 두세요.

한도와 오류 처리

아웃바운드 SIP 요청은 1 MiB 바디 한도가 있어요. 링잉은 3분, 연결된 통화는 2시간으로 제한돼요. 이 한도는 생성 요청에서 설정할 수 없어요. outbound_sip_not_enabled를 담은 403 응답은 조직에 아웃바운드 통화가 활성화되지 않았다는 뜻이에요. 잘못된 세션 구성은 생성 요청에서 반환되고, 전송 설정 실패는 502, 초기화 타임아웃은 504를 반환할 수 있어요. 생성 성공 후에는 비동기 설정 실패를 위해 transport.failed를 모니터링하세요. 각 생성 요청은 새 통화를 걸어요. X-Client-Request-Id는 요청을 중복 제거하지 않아요. 모호한 타임아웃·연결 실패 후 자동 재시도하지 마세요 — 재시도가 통화를 또 걸 수 있어요.

서버 오디오 브리지

애플리케이션이 전화 제공자나 에이전트 프레임워크에서 오디오 스트림을 받으면 GPT-Live WebSocket 연결을 써요. 애플리케이션이 두 연결을 인증하고, 이벤트 봉투를 변환하고, 오디오를 양방향으로 중계해요. GPT-Live는 WebSocket 위에서 8kHz raw G.711 μ-law·A-law 오디오를 지원해요. 제공자 스트림이 같은 코덱·샘플 속도·채널 수를 쓴다면, 애플리케이션이 raw 오디오 바이트를 PCM으로 변환하지 않고 전달할 수 있어요. 오디오 순서를 보존하고 각 연결이 요구하는 메시지 형식으로 오디오 바이트를 감싸요. 브리지는 큐된 오디오, 인터럽트, 통화 종료를 관리하세요. 재생을 처리할 때 제공자가 버퍼링한 오디오를 고려하세요. Live 세션 수명 주기는 Managing sessions, 턴 테이킹·재생 제어 변경은 Migrate to GPT-Live을 참고하세요. 제공자의 통화·방 식별자를 OpenAI 세션 ID와 함께 보관해 두 시스템에서 대화를 추적할 수 있게 하세요.


Realtime API: 전화 연결

SIP는 인터넷으로 전화를 거는 프로토콜이에요. SIP와 Realtime API로 들어오는 전화를 API로 연결할 수 있어요. 전화번호를 Realtime API에 연결하려면 SIP 트렁킹 제공자(예: Twilio)를 써요. 이 서비스가 전화를 IP 트래픽으로 변환해요. 제공자에서 전화번호를 구매한 뒤 아래 절차를 따르세요.

먼저 platform.openai.com 설정 > Project > Webhooks에서 인입 통화용 웹훅을 만들고, 웹훅을 구성한 프로젝트 ID로 SIP 트렁크를 OpenAI SIP 엔드포인트에 연결해요(예: sip:[email protected];transport=tls). 유럽 데이터 리전시는 sip:[email protected];transport=tls를 써요. $PROJECT_ID는 설정 > Project > General에서 찾을 수 있고 proj_ 접두사가 있어요.

OpenAI가 프로젝트와 연관된 SIP 트래픽을 받으면 웹훅이 발동돼요. 발생하는 이벤트는 realtime.call.incoming 이벤트예요. 이 웹훅에서 웹훅의 call_id 값으로 통화를 수락하거나 거절할 수 있어요. 수락할 때 Realtime API 세션에 필요한 구성(지침, 음성 등)을 제공해요. 세션이 설정되면 WebSocket을 열어 평소처럼 세션을 모니터링할 수 있어요. 수락·거절·모니터링·refer·hangup API는 아래에 문서화돼 있어요.

통화 수락

Accept call 엔드포인트로 인바운드 통화를 승인하고 응답할 realtime 세션을 구성해요. create client secret 요청에 보내는 것과 같은 파라미터를 보내요 — 즉 통화를 모델에 브리징하기 전에 realtime 모델, 음성, 도구, 지침이 설정되어 있어야 해요.

curl -X POST "https://api.openai.com/v1/realtime/calls/$CALL_ID/accept" \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
        "type": "realtime",
        "model": "gpt-realtime-2.1",
        "instructions": "You are Alex, a friendly concierge for Example Corp."
      }'

요청 경로에 realtime.call.incoming 웹훅의 call_id가 포함되어야 하고, 각 요청마다 Authorization 헤더가 필요해요. SIP 레그가 링잉되고 realtime 세션이 설정되면 엔드포인트가 200 OK를 반환해요.

통화 거절

Reject call 엔드포인트로 처리하고 싶지 않은 인입 통화(예: 지원되지 않는 국가 코드)를 거절해요. call_id 경로 파라미터와 JSON 바디의 선택적 SIP status_code(예: "부재중"을 나타내는 486)를 제공해 캐리어로 보내는 응답을 제어해요.

curl -X POST "https://api.openai.com/v1/realtime/calls/$CALL_ID/reject" \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"status_code": 486}'

상태 코드를 제공하지 않으면 API는 기본적으로 603 Decline을 써요. OpenAI가 SIP 응답을 전달하면 성공 요청이 200 OK로 응답해요.

통화 이벤트 모니터링

통화를 수락한 뒤 같은 세션에 WebSocket을 열어 이벤트를 스트리밍하고 실시간 명령을 내려요. call_id 파라미터로 기존 통화에 연결할 때는 model 인자가 쓰이지 않아요 (이미 accept 엔드포인트로 구성됐기 때문). WebSocket은 GET wss://api.openai.com/v1/realtime?call_id={call_id}이고, 다른 Realtime API 연결과 똑같이 동작해요. response.create 같은 클라이언트 이벤트를 보내고 서버 이벤트를 들어 진행을 추적해요. 자세한 내용은 Webhooks and server-side controls을 참고하세요.

통화 리다이렉트

Refer call 엔드포인트로 활성 통화를 전환해요. call_id와 SIP Refer-To 헤더에 넣을 target_uri(예: tel:+141****0123 또는 sip:[email protected])를 제공해요.

curl -X POST "https://api.openai.com/v1/realtime/calls/$CALL_ID/refer" \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"target_uri": "tel:+141****0123"}'

REFER가 SIP 제공자에게 중계되면 OpenAI가 200 OK를 반환하고, 다운스트림 시스템이 나머지 통화 흐름을 처리해요.

통화 끊기

Hang up 엔드포인트로 세션을 종료해요. 이 끝점은 SIP와 WebRTC realtime 세션 모두를 종료할 수 있어요.

curl -X POST "https://api.openai.com/v1/realtime/calls/$CALL_ID/hangup" \
  -H "Authorization: Bearer ***"

API는 통화 철수를 시작할 때 200 OK로 응답해요.

SIP 시그널링·미디어 IP 범위

Realtime SIP 통화는 시그널링과 미디어에 별도의 네트워크 경로를 써요. 올바른 동작을 위해 네트워크가 아래처럼 시그널링·미디어 트래픽을 허용하도록 구성하세요.

SIP 시그널링 — sip.api.openai.com과 sip-eu.api.openai.com은 GeoIP 라우팅되는 엔드포인트예요. 네트워크가 DNS가 반환하는 주소로 포트 5061에 대한 아웃바운드 TCP/TLS 트래픽을 허용해야 해요.

SRTP 미디어 — API가 협상된 SDP에서 별도의 미디어 IP 주소와 UDP 포트를 지정해요. 네트워크가 다음 CIDR로부터/로의 양방향 UDP SRTP 트래픽을 허용해야 해요.

  • 13.79.45.80/28
  • 23.98.140.64/28
  • 40.67.149.176/28
  • 40.83.204.240/28

서버 예시

다음은 realtime.call.incoming 핸들러 예시예요. 통화를 수락하고 Realtime API의 모든 이벤트를 로그로 남겨요. Ruby 예시는 OPENAI_API_KEY와 OPENAI_WEBHOOK_SECRET을 설정하고 gem install openai webrick async-websocket으로 의존성을 설치해야 해요.

Python (Flask) 예시는 웹훅을 검증하고, realtime.call.incoming이 오면 웹훅의 call_id로 accept 요청을 보내고, 백그라운드 스레드에서 wss://api.openai.com/v1/realtime?call_id=...에 WebSocket을 열어 response.create를 보내고 이벤트를 받아요. Ruby(WEBrick)는 같은 흐름을 client.realtime.calls.accept와 client.realtime.connect_to_call로 구현해요.

더 알아보기 (Learn more)

다음 단계: Realtime prompting guide, Managing conversations, Webhooks and server-side controls, Managing costs, Realtime transcription. 추가 자료: JavaScript demo, Twilio Elastic SIP Trunking에 Realtime SIP Connector 연결.