|
문서
데모 예약플랫폼
플랫폼MCPCLIAPI
워크플로
가이드변경 로그

환영합니다

  • 개요
  • 인증
  • 오류 및 상태 코드
  • Webhook 서명

로컬라이제이션

  • 개요
  • 작업 생성
  • 번역 제외 키 잠그기
  • 작업 그룹 추적하기
  • 단일 작업 조회
  • 작업 목록 조회
  • Webhook 전송
  • 실시간 진행 상황(WebSocket)

파이프라인

  • 개요
  • 로컬라이제이션 전 AI 편집
  • 사람 검토
  • AI 평가(사후 편집)
  • 자연스러운 카피로 다듬기
  • 역번역 검사
  • 파이프라인 설정
  • 파이프라인 실행 추적하기

프로비저닝

  • 개요
  • 프로비저닝 작업 생성
  • 소스 유형
  • AI가 추출하는 항목
  • Webhook 전송
  • 실시간 진행 상황(WebSocket)

동기식

  • Localize
  • Recognize

엔진 관리

  • 엔진 제안

Webhook 전송

작업 그룹을 생성하고 몇 밀리초 만에 202 응답을 받았습니다. 이제 번역은 백그라운드에서 진행 중이며, 로캘마다 작업이 하나씩 실행됩니다. 완료될 때까지 각 작업을 폴링할 수도 있겠지만, 독일어가 준비됐는지 확인하려고 폴링 루프를 돌리고 싶지는 않을 겁니다. 각 로캘이 준비되는 즉시 서버가 통지를 받는 편이 훨씬 낫습니다.

바로 그 역할을 webhook이 합니다. 작업을 만들 때 callbackUrl를 전달하면, 각 작업이 최종 상태에 도달하는 순간 Lingo가 해당 URL로 결과를 POST합니다. 즉, 로캘마다 POST가 한 번씩, 준비되는 즉시 전송됩니다. 문제없이 번역된 로캘은 데이터와 함께 translation.completed로 도착합니다. 실패한 로캘은 오류와 함께 translation.failed로 도착합니다. 별도로 조회하지 않아도 언어별 결과를 모두 받아볼 수 있습니다.

이 페이지에서는 두 가지 페이로드와 이를 처리하는 방법을 설명합니다. 전송은 서명되며 재시도되고, 이 메커니즘은 프로비저닝과 공통으로 사용됩니다. 자세한 내용은 webhook 서명 검증 페이지에 정리되어 있으며, 필요한 지점마다 해당 페이지로 연결됩니다.

이 페이지에서 다루는 내용

  • 전송 방식
  • 완료된 페이로드
  • 실패한 페이로드
  • Webhook 처리
  • 전송이 적합하지 않은 경우

전송 방식#

그룹의 각 로캘은 서로 독립적인 작업입니다. 하나가 최종 상태에 도달하는 즉시 그 결과만 여러분의 callbackUrl로 전송됩니다. Lingo는 가장 느린 로캘을 기다리지 않으며, 그룹 전체를 하나의 호출로 묶지도 않습니다. 대상 로캘이 14개라면 최대 14번의 POST가 발생하고, 각 언어가 끝나는 순서대로 도착합니다.

요청별 대상은 callbackUrl를 사용해 작업 그룹을 생성할 때 설정할 수 있고, 대시보드에서 조직 기본값을 설정해 모든 그룹이 이를 상속받게 할 수도 있습니다. 요청별 callbackUrl는 해당 그룹에서 조직 기본값보다 우선합니다.

HTTPS만 지원

callbackUrl는 반드시 HTTPS를 사용해야 합니다. HTTP URL은 작업 생성 시 400과 함께 거부됩니다. webhook은 서명되는데, 평문 연결 위로 서명된 페이로드를 보내면 그 의미가 사라지기 때문입니다.

전송되는 페이로드는 두 가지 형태이며, type 필드로 구분됩니다: translation.completed와 translation.failed입니다. 둘 다 자신이 속한 작업과 그룹, 그리고 해당 로캘을 담고 있으므로 하나의 핸들러에서 type를 기준으로 분기해 올바른 레코드를 업데이트할 수 있습니다.

알 수 없는 이벤트 타입도 무리 없이 처리하세요

현재 전송되는 타입은 translation.completed와 translation.failed입니다. 하지만 이 집합은 앞으로 확장될 수 있다고 보고 처리해야 합니다. 아는 타입만 분기하고 나머지는 무시하면, 향후 새로운 이벤트 타입이 추가되어도 이미 배포된 핸들러가 깨지지 않습니다.

완료된 페이로드#

작업이 성공적으로 끝나면 페이로드에는 번역된 data가 담깁니다. 형태는 작업을 가져올 때 받는 것과 같지만, 폴링하는 대신 푸시로 전달된다는 점이 다릅니다. data는 여러분이 제출한 구조를 그대로 반영합니다. 모든 문자열은 번역되고, 모든 비문자열 값(숫자, 불리언, null)은 그대로 유지되며, 중첩 구조도 보존됩니다.

json
{
  "type": "translation.completed",
  "jobId": "ljb_A1b2C3d4E5f6G7h8",
  "groupId": "ljg_A1b2C3d4E5f6G7h8",
  "sourceLocale": "en",
  "targetLocale": "de",
  "data": {
    "id": "course_101",
    "title": "Einführung in maschinelles Lernen",
    "steps": [
      { "heading": "Was ist ML?", "body": "Maschinelles Lernen ist ein Teilbereich der künstlichen Intelligenz." },
      { "heading": "Überwachtes Lernen", "body": "Trainieren eines Modells mit gelabelten Daten." }
    ],
    "metadata": { "author": "Dr. Smith", "difficulty": "beginner" }
  }
}
필드설명
typetranslation.completed
jobId완료된 작업(ljb_ 접두사)
groupId이 작업이 속한 그룹(ljg_ 접두사)
sourceLocale여러분이 제출한 소스 로캘
targetLocale이 페이로드가 번역된 대상 로캘
data여러분이 제출한 data와 구조가 일치하는 번역된 콘텐츠

출력이 생성된 작업은 실패가 아닙니다. 따라서 completed_with_warnings로 끝난 작업(출력은 생성됐지만 선택적 pipeline 단계가 누락된 경우)도 사용 가능한 data와 함께 translation.completed로 전송됩니다. webhook은 해당 로캘이 준비됐다는 사실을 알려주고, 왜 그 단계가 누락됐는지에 대한 단계별 경고는 단일 작업에 남습니다. 필요할 때 jobId로 조회하면 됩니다.

실패한 페이로드#

로캘 단위로 실패가 발생할 수 있습니다. 모델이 타임아웃될 수도 있고, 구성된 모든 모델을 사용할 수 없을 수도 있습니다. 작업이 failed에 도달해도 여러분은 여전히 통지를 받습니다. 이때 페이로드 타입은 translation.failed이며, data 대신 error 문자열이 담깁니다:

json
{
  "type": "translation.failed",
  "jobId": "ljb_C3d4E5f6G7h8I9j0",
  "groupId": "ljg_A1b2C3d4E5f6G7h8",
  "sourceLocale": "en",
  "targetLocale": "ja",
  "error": "Model timeout after 30 seconds"
}
필드설명
typetranslation.failed
jobId실패한 작업
groupId이 작업이 속한 그룹
sourceLocale여러분이 제출한 소스 로캘
targetLocale실패한 로캘
error사람이 읽을 수 있는 실패 설명

실패는 하나의 로캘에만 한정됩니다. de, fr, ja를 제출했고 ja가 실패했다면, 해당 로캘에 대한 translation.failed POST가 별도로 전송되고, de와 fr는 translation.completed로 도착합니다. 즉 독일어와 프랑스어 번역은 그대로 전달됩니다. 그룹의 partial-failure status는 이런 혼합 상태를 반영합니다. 실패한 로캘을 복구하려면 새 멱등성 키로 해당 로캘만 대상으로 새 작업을 제출하세요.

Webhook 처리#

여기서 가장 먼저 드는 의문은 아주 타당합니다. 내 핸들러는 실제 작업을 처리하는데요. 데이터베이스 쓰기, 캐시 무효화, 연결된 클라이언트로의 팬아웃 같은 작업까지 하면 webhook이 타임아웃될 만큼 연결이 오래 열려 있지 않나요?

맞습니다. 그래서 Lingo를 기다리게 하면 안 됩니다. 먼저 200을 반환하고, 그다음 처리하세요. 수신을 즉시 확인하고 실제 작업은 응답을 보낸 뒤에 수행하세요. 빠르게 응답하는 핸들러는 전송 상태를 안정적으로 유지하지만, 다운스트림 작업으로 블로킹되는 핸들러는 불필요한 재시도를 부를 수 있습니다.

javascript
app.post("/webhooks/translations", verifyWebhook, async (req, res) => {
  // Acknowledge first - one POST per locale, the moment it lands.
  res.status(200).send("ok");

  const { type, jobId, groupId, targetLocale, data } = req.body;

  if (type === "translation.completed") {
    await db.content.update({
      where: { groupId },
      data: { [`content_${targetLocale}`]: data },
    });

    // Advance your own progress model - your UI can poll this or receive it over SSE.
    await db.translationProgress.increment({
      where: { groupId },
      data: { completedLanguages: { increment: 1 } },
    });
  }

  if (type === "translation.failed") {
    console.error(`Translation failed: ${jobId} (${targetLocale})`, req.body.error);
  }
});

이 페이지에서 다루지 않는 유일한 부분은 verifyWebhook 미들웨어입니다. 모든 전송은 Standard Webhooks 사양에 따라 서명되므로, 별도로 추측하거나 역공학할 필요가 없습니다. 검증 방법과 2xx가 아닌 응답 이후의 재시도 일정은 프로비저닝과 공통으로 사용하는 webhook 서명 검증 문서에 자세히 나와 있습니다. 페이로드를 신뢰하기 전에 이 미들웨어를 먼저 연결하세요. 검증되지 않은 본문은 인증되지 않은 본문입니다.

본문을 신뢰하기 전에 먼저 검증하세요

엔드포인트는 공개 URL이므로 누구나 POST할 수 있습니다. 어떤 페이로드든 처리하기 전에 원시 요청 본문을 기준으로 서명을 검증하세요. 방법 자체, 즉 헤더, HMAC, whsec_ secret에 관한 내용은 서명 검증 페이지에 있습니다.

전송이 적합하지 않은 경우#

webhook은 푸시 편의 기능일 뿐, 시스템 오브 레코드는 아닙니다. 다른 방법이 더 적합한 경우는 두 가지이며, 둘 다 링크 한 번이면 확인할 수 있습니다.

결과가 전송될 때 엔드포인트가 다운돼 있었다면 플랫폼이 재시도합니다. 그리고 모든 재시도가 소진되더라도 결과가 사라지지는 않습니다. 결과는 jobId로 계속 조회 가능하며, 작업의 callbackStatus에는 푸시가 최종적으로 성공했는지가 기록됩니다. 재시도 일정 자체는 서명 및 전송 페이지에 나와 있습니다. 일반적인 경우 webhook은 폴링 루프를 덜어주고, 드문 경우에도 그 아래에는 항상 작업 레코드가 남아 있습니다.

그리고 여러분이 원하는 것이 서버로 들어오는 로캘별 콜백이 아니라, 로캘이 도착할 때마다 14개 중 3개에서 4개로 올라가는 카운터처럼 UI에서 실시간 진행 상황을 보는 것이라면, 필요한 것은 webhook이 아니라 작업 그룹 WebSocket입니다.

실시간 진행 상황(WebSocket)
서버로 들어오는 로캘별 콜백 대신 전체 상태 스냅샷으로 그룹 진행 상황을 UI에 스트리밍하세요.
Webhook 서명 검증
서명을 검증하고, 헤더를 확인하고, 재시도 일정까지 처리하세요. 모든 webhook 전송에 공통으로 적용됩니다.
단일 작업 가져오기
경고를 포함해 모든 결과를 jobId로 가져오세요. 모든 전송의 기반이 되는 진실 공급원입니다.

이 페이지가 도움이 되었나요?

Max PrilutskiyMax Prilutskiy·업데이트됨 12일 전·5 min read