Webhook
폴링을 통해 같은 엔드포인트를 여러 번 요청하지 않고, webhook을 설정하여 여러분의 웹 서버 엔드포인트로 알림을 받아볼 수 있습니다.
info
Webhook 기능을 사용하시려면 Shakr의 고객성공팀에 연락하셔서 사용하시고자 하는 엔드포인트를 알려주세요. 등록이 완료되면 해당 엔드포인트로 POST 요청이 전달됩니다.
사용 가능한 webhook 목록
이벤트 이름 | 설명 |
---|---|
batch_show.status.changed | BatchShow의 status 가 변경됨 |
feed.status.changed | Feed의 status 가 변경됨 |
render_session.status.changed | RenderSession의 status 가 변경됨 |
render_session.external_upload.complete | RenderSession에 지정한 external_upload (Automated Video Delivery)가 완료됨 |
template_style_version.status.changed | TemplateStyleVersion의 status 가 변경됨 |
예제
render_session.status.changed
RenderSession의 status
가 변경되면 발생하는 이벤트입니다. RenderSession status
의 목록은 다음과 같습니다.
Status | 설명 |
---|---|
draft | RenderSession이 생성되었고 편집을 할 수 있는 상태 (초기 상태) |
preview_rendering | RenderSession 렌더가 진행중인 상태 |
preview_rendered | RenderSession 렌더가 완료된 상태 |
preview_rendered
status는 RenderSession의 렌더가 완료되었다는 것을 의미하고, 해당 status인 경우 video_url
을 통해 비디오 파일을 다운로드 받을 수 있습니다.
Webhook으로 전달되는 예시 페이로드는 다음과 같습니다.
{
"render_session": {
"id": "deJIKN",
"title": "나의 RenderSession",
"status": "preview_rendered",
...
"render_progress": 100,
"thumbnails": [...],
"video_url": "...",
...
}
}
feed.status.changed
Feed의 status
가 변경되면 발생하는 이벤트입니다. Feed status
의 목록은 다음과 같습니다.
Status | Description |
---|---|
pending | Feed가 생성되고 처리중인 상태 (초기 상태) |
postprocessing | Feed에 추가적인 처리가 필요한 경우, 이를 진행중인 상태 |
ready | Feed 처리를 완료하여 사용 가능한 상태 |
failed | Feed 처리를 실패한 상태 |
Webhook으로 전달되는 예시 페이로드는 다음과 같습니다.
{
"feed": {
"id": "zknqQo",
"status": "ready",
"upload": { ... },
...
}
}
render_session.external_upload.complete
RenderSession을 생성할 때 external_uploads
파라미터를 지정하여 Automated Video Delivery 기능을 사용한 이후, 렌더가 완료되어 external_uploads
중 하나의 업로드가 완료되었을 때 발생하는 이벤트입니다. Automated Video Delivery에서 지원하는 모든 채널에서 webhook을 지원합니다.
각 채널별 예시 페이로드는 다음과 같습니다.
- Facebook Ad Account
- AWS S3
- Azure Blob Storage
{
"render_session_id": "RENDER_SESSION_ID",
"to": "fbadact://act_123",
"remote_id": "CREATED_FACEBOOK_ADVIDEO_ID"
}
지정한 Facebook Ad Account에 새로 추가된 AdVideo ID를 remote_id
키를 통해 반환합니다.
{
"render_session_id": "RENDER_SESSION_ID",
"to": "s3://bucket/path/to/object.mp4",
"remote_id": "path/to/object.mp4"
}
RenderSession 생성 시 지정한 to
키에 버킷 이름과 경로가 모두 포함되어 있습니다. remote_id
키는 파일 경로입니다.
{
"render_session_id": "RENDER_SESSION_ID",
"to": "wasbs://container@storageaccount.blob.core.windows.net/path/to/object.mp4",
"remote_id": "path/to/object.mp4"
}
RenderSession 생성 시 지정한 to
키에 컨테이너 이름, Storage Account 이름, 경로가 모두 포함되어 있습니다. remote_id
키는 파일 경로입니다.
batch_show.status.changed
BatchShow의 status
가 변경되면 발생하는 이벤트입니다. BatchShow status
의 목록은 다음과 같습니다.
Status | 설명 |
---|---|
draft | BatchShow가 생성되었고 편집을 할 수 있는 상태 (초기 상태) |
preview_rendering | BatchShow의 일부 결과물 렌더가 진행중인 상태 (프리뷰 렌더중) |
preview_rendered | BatchShow의 일부 결과물 렌더가 완료된 상태 (프리뷰 렌더 완료) |
rendering | BatchShow의 전체 결과물 렌더가 진행중인 상태 |
rendered | BatchShow의 전체 결과물 렌더가 완료된 상태 |
Webhook으로 전달되는 예시 페이로드는 다음과 같습니다.
{
"batch_show": {
"id": "zbkqwA",
"title": "나의 BatchShow",
"status": "preview_rendered",
"rendered_outputs_count": 3,
"failed_outputs_count": 0,
"feed_items_count": 10,
...
}
}
template_style_version.status.changed
TemplateStyleVersion의 status
가 변경되었을 때 발생하는 이벤트입니다. TemplateStyleVersion의 status
목록은 다음과 같습니다.
Status | 설명 |
---|---|
created | TemplateStyleVersion이 생성된 상태 (초기 상태) |
processing | Shakr에서 업로드된 프로젝트 파일을 분석하고 있는 상태 |
demo_rendering | Shakr에서 템플릿의 테스트 렌더링을 진행중인 상태 |
edit_ready | Template Wizard에서 사용이 가능한 상태 |
서명 검증하기
여러분의 웹 서버의 webhook 엔드포인트는 외부에 노출되어 있기 때문에, 엔드포인트의 존재를 알게 된 외부 공격자가 악의적인 요청을 보낼 가능성도 있습니다. 이러한 공격을 방지하기 위하여, 모든 webhook 요청에는 SHA-256 서명이 포함되어 있어 요청이 Shakr에서 온 것인지를 직접 검증할 수 있습니다.
Webhook 요청의 X-Shakr-Signature
헤더를 통해 서명이 전달되고, 이 서명은 API 액세스 토큰을 발급받을 때 사용하는 client_id
으로 다음과 같이 검증할 수 있습니다.
- Ruby
- Elixir
- Node.js
require "json"
require "rack"
digest = OpenSSL::Digest.new("sha256")
# Get your client_id, used when retrieving an access token
client_id = "YOUR_SHAKR_CLIENT_ID"
# Get "X-Shakr-Signature" header from Shakr API response
# Example: request.env["HTTP_X_SHAKR_SIGNATURE"] when using Sinatra
returned_signature = "HEADER_RETURNED_FROM_SHAKR_API"
# Get raw body returned from Shakr API response
# Example: request.body when using Sinatra
body = "BODY_RETURNED_FROM_SHAKR_API"
calculated_signature = OpenSSL::HMAC.hexdigest(digest, client_id, body)
calculated_signature_str = "sha256=#{calculated_signature}"
# Use secure comparison method to prevent timing attack
# Returns true if matches
Rack::Utils.secure_compare(returned_signature, calculated_signature_str)
# Get your client_id, used when retrieving an access token
client_id = "YOUR_SHAKR_CLIENT_ID"
# Get "X-Shakr-Signature" header from Shakr API response
# Example: hd(Plug.Conn.get_req_header(conn, "x-shakr-signature")) when using Plug
returned_signature = "HEADER_RETURNED_FROM_SHAKR_API"
# Get raw body returned from Shakr API response
# Example: {:ok, body, conn} = Plug.Conn.read_body(conn) when using Plug
body = "BODY_RETURNED_FROM_SHAKR_API"
calculated_signature =
:hmac
|> :crypto.mac(:sha256, client_id, body)
|> Base.encode16(case: :lower)
calculated_signature_str = "sha256=#{calculated_signature}"
# Use secure comparison method to prevent timing attack
# Returns true if matches
Plug.Crypto.secure_compare(returned_signature, calculated_signature_str)
const crypto = require('crypto');
// Get your client_id, used when retrieving an access token
const client_id = 'YOUR_SHAKR_CLIENT_ID';
// Get "X-Shakr-Signature" header from Shakr API response
// Example: req.headers.HTTP_X_SHAKR_SIGNATURE when using expressjs
const returned_signature = 'HEADER_RETURNED_FROM_SHAKR_API';
// Get raw body returned from Shakr API response
// Example: req.body when using expressjs
const body = 'BODY_RETURNED_FROM_SHAKR_API';
const calculated_signature = crypto.createHmac('sha256', client_id).update(body, 'utf-8').digest('hex');
const calculated_signature_str = `sha256=${calculated_signature}`;
// Use secure comparison method to prevent timing attack
// Returns true if matches
crypto.timingSafeEqual(Buffer.from(returned_signature, 'utf-8'), Buffer.from(calculated_signature_str, 'utf-8'));
서명을 비교하기 전에 sha256=
텍스트를 앞에 추가하는 것을 잊지 마세요.
타이밍 공격 방지하기
많은 프로그래밍 언어의 ==
비교 연산자는 타이밍 공격(Timing Attack)을 방지해주지 않습니다. Ruby Rack의 secure_compare
, PHP의 hash_equals
, Python의 hmac.compare_digest
와 같은 상수 시간 문자열 비교(Constant time string comparison) 연산을 사용하시기 바랍니다.