웹훅
개요
웹훅을 사용하면 추출이 완료되거나 실패할 때 실시간 알림을 받을 수 있습니다. API를 폴링하는 대신, 상태가 변경되면 DocMap이 추출 결과와 함께 엔드포인트로 POST 요청을 보냅니다.
주요 사항:
- 계정당 최대 10개의 활성 웹훅
- 페이로드는 HMAC-SHA256으로 서명되어 진위를 검증할 수 있습니다
- 전송 타임아웃은 웹훅당 10초
- 전송은 실행 후 잊기 방식입니다 -- 하나의 웹훅이 실패해도 다른 웹훅은 차단되지 않습니다
웹훅 생성
대시보드에서
- DocMap 대시보드에서 웹훅으로 이동합니다
- 웹훅 생성을 클릭합니다
- 엔드포인트 URL을 입력합니다 (프로덕션에서는 HTTPS 필수)
- 구독할 이벤트를 선택합니다
- 서명 시크릿을 즉시 복사합니다 -- 다시 표시되지 않습니다
API에서
curl -X POST https://api.docmap.io/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/docmap",
"events": ["extraction.completed", "extraction.failed"]
}'const response = await fetch("https://api.docmap.io/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://your-app.com/webhooks/docmap",
events: ["extraction.completed", "extraction.failed"],
}),
});
const { data } = await response.json();
// Store this immediately -- it will never be shown again
console.log("Signing Secret:", data.secret);
console.log("Webhook ID:", data.webhook.id);import requests
response = requests.post(
"https://api.docmap.io/v1/webhooks",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"url": "https://your-app.com/webhooks/docmap",
"events": ["extraction.completed", "extraction.failed"],
},
)
data = response.json()["data"]
# Store this immediately -- it will never be shown again
print("Signing Secret:", data["secret"])
print("Webhook ID:", data["webhook"]["id"])WARNING
서명 시크릿은 웹훅 생성 시 한 번만 반환됩니다. 환경 변수 또는 시크릿 관리자에 안전하게 저장하세요.
페이로드 형식
이벤트가 발생하면 DocMap은 다음 구조로 웹훅 URL에 POST 요청을 보냅니다:
{
"event": "extraction.completed",
"data": {
"id": "extract-abc123",
"userId": "uid_xyz789",
"templateId": "tpl_inv001",
"templateName": "Standard Invoice",
"fileName": "invoice-march.pdf",
"status": "completed",
"extractedData": {
"vendor_name": "Acme Corp",
"invoice_number": "INV-2024-003",
"total_amount": 4750.00
},
"error": null,
"variables": [...],
"source": "api",
"runId": null,
"processingTimeMs": 3842,
"createdAt": "2024-11-20T14:30:00.000Z"
},
"timestamp": "2024-11-20T14:30:04.000Z"
}헤더
각 웹훅 요청에는 다음 헤더가 포함됩니다:
| 헤더 | 설명 |
|---|---|
Content-Type | application/json |
X-DocMap-Signature | HMAC-SHA256 서명: sha256={hex_digest} |
X-DocMap-Event | 이벤트 이름 (예: extraction.completed) |
서명 검증
요청이 DocMap에서 온 것인지 확인하려면 항상 X-DocMap-Signature 헤더를 검증하세요. 서명은 서명 시크릿을 키로 사용하여 원시 요청 본문의 HMAC-SHA256 해시입니다.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verifyWebhookSignature(
body: string,
signature: string,
secret: string,
): boolean {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex')
const received = signature.replace('sha256=', '')
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex'),
)
}
// Express example
app.post('/webhooks/docmap', (req, res) => {
const signature = req.headers['x-docmap-signature'] as string
const body = JSON.stringify(req.body)
if (!verifyWebhookSignature(body, signature, process.env.DOCMAP_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature')
}
const { event, data } = req.body
console.log(`Received ${event} for extraction ${data.id}`)
// Process the webhook...
res.status(200).send('OK')
})import hmac
import hashlib
def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
body,
hashlib.sha256,
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
# Flask example
from flask import Flask, request
app = Flask(__name__)
@app.post('/webhooks/docmap')
def handle_webhook():
signature = request.headers.get('X-DocMap-Signature', '')
body = request.get_data()
if not verify_webhook_signature(body, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
payload = request.get_json()
event = payload['event']
data = payload['data']
print(f"Received {event} for extraction {data['id']}")
# Process the webhook...
return 'OK', 200TIP
타이밍 공격을 방지하려면 항상 상수 시간 비교(timingSafeEqual 또는 hmac.compare_digest)를 사용하세요.
이벤트 레퍼런스
| 이벤트 | 트리거 |
|---|---|
extraction.completed | 추출이 성공적으로 완료되었습니다. data.status는 "completed"이고 data.extractedData에 결과가 포함됩니다. |
extraction.failed | 추출이 실패했습니다. data.status는 "failed"이고 data.error에 오류 메시지가 포함됩니다. |
모범 사례
HTTPS를 사용하세요. 웹훅 URL에는 항상 HTTPS 엔드포인트를 사용하세요. HTTP 엔드포인트는 프로덕션에서 거부될 수 있습니다.
서명을 검증하세요. 웹훅을 처리하기 전에 항상
X-DocMap-Signature헤더를 검증하세요. 이는 요청이 DocMap에서 왔으며 변조되지 않았음을 확인합니다.빠르게 응답하세요. 몇 초 이내에 2xx 상태 코드를 반환하세요. DocMap은 응답을 최대 10초까지 기다립니다. 처리가 더 오래 걸리는 경우 웹훅을 즉시 확인하고 비동기적으로 처리하세요.
중복을 처리하세요. 드문 경우 웹훅이 두 번 이상 전송될 수 있습니다. 필요한 경우 추출
id를 사용하여 중복을 제거하세요.시크릿을 안전하게 저장하세요. 서명 시크릿을 환경 변수 또는 시크릿 관리자에 보관하세요. 소스 코드에 하드코딩하거나 버전 관리에 커밋하지 마세요.
실패를 모니터링하세요. 엔드포인트가 지속적으로 실패하는 경우 서버 로그를 확인하세요. DocMap은 전송 실패를 기록하지만 재시도하지는 않습니다.
