본문으로 건너뛰기
개발자 리소스웹훅 연동하기

웹훅 연동하기

다음 절차에 따라 웹훅 엔드포인트를 만들고 등록하면 앱에서 웹훅 이벤트를 수신할 수 있습니다.

  1. 이벤트 데이터 POST 요청을 수신할 웹훅 엔드포인트 핸들러를 만드세요.
  2. Tokenz 대시보드에서 엔드포인트를 등록하세요.
  3. 웹훅 엔드포인트의 보안을 설정하세요.
  4. 테스트 결제로 웹훅 엔드포인트를 테스트하세요.

1. 핸들러 생성

웹훅 POST 요청을 받을 HTTPS 엔드포인트를 구현하세요. Webhook.site로 페이로드를 확인하고 전송을 테스트할 수 있습니다.

이 엔드포인트는 다음 조건을 충족해야 합니다.

  • 이벤트 객체로 구성된 JSON 페이로드의 POST 요청을 처리합니다.
  • 시간 초과를 유발할 수 있는 복잡한 로직을 실행하기 전에 성공 상태 코드(2xx)를 신속하게 반환합니다.

2. 엔드포인트 등록

대시보드에서 웹훅 엔드포인트를 등록하세요.

  1. 개발자(Developers) 페이지에서 웹훅(Webhooks) 탭을 여세요.
  2. 새 웹훅 생성(Create new webhook)을 클릭하세요.
  3. URL과 선택 사항인 설명을 입력하고, 수신할 이벤트를 선택하세요. 고객의 주문 성공을 놓치지 않도록 최소한 order.succeeded를 선택하는 것을 권장합니다.
  4. 웹훅 생성(Create webhook)을 클릭하세요.
  5. 반환된 서명 비밀키를 복사하여 보관하세요. 수신 웹훅 검증에 필요합니다. 보안상 한 번만 표시됩니다.

3. 웹훅 엔드포인트 보안 설정

Tokenz는 전송하는 모든 웹훅에 서명을 포함합니다. Tokenz가 전송했고 변조되지 않았는지 확인하려면 반드시 서명을 검증해야 합니다. 또는 웹훅 알림을 계기로 API에서 주문 상태 변경을 확인할 수 있습니다. 불필요한 API 호출을 피하려면 웹훅 서명 검증을 권장합니다.

서명된 이벤트의 Tokenz-Signature 헤더에는 타임스탬프(t:<unix-timestamp-in-milliseconds>)와 검증해야 할 버전별 서명(v1:<HMAC signature>)이 하나 이상 포함됩니다. 현재 유효한 라이브 서명 방식은 v1입니다.

예제:

plaintext
t:1725864981111,v1:zjdrra3...Mh+e6s=

서명 검증

1. 타임스탬프와 서명 추출

헤더를 쉼표(,)로 나누어 요소 목록을 만드세요. 각 요소를 콜론(:)으로 나누어 접두사와 값의 쌍을 얻습니다. t는 타임스탬프, v1은 서명입니다. 나머지 요소는 무시하세요.

2. 서명할 페이로드 준비

다음 값을 순서대로 이어 붙여 페이로드 문자열을 만드세요.

  • 문자열 형태의 타임스탬프
  • 수정하지 않은 원본 요청 본문(JSON 페이로드)

3. 서명 계산

SHA256 해시 함수를 사용하여 HMAC을 계산하세요. 엔드포인트의 서명 비밀키를 키로, 페이로드 문자열을 메시지로 사용합니다.

4. 서명 비교

헤더의 서명과 계산한 서명이 일치하는지 확인하세요. 재전송 공격을 방지하려면 현재 시각과 수신 타임스탬프의 차이도 확인하세요. 서명이 유효해도 타임스탬프가 오래되었다면 거부할 수 있습니다. 권장 허용 오차는 300초(5분)입니다.

재전송 공격은 공격자가 정상 페이로드와 서명을 가로채 다시 보내는 공격입니다. Tokenz는 Tokenz-Signature 헤더에 타임스탬프를 포함하여 이 위험을 줄입니다.

javascript
const crypto = require('crypto');
const express = require('express');
const app = express();
const port = 3000;

// Use express.raw() so the signature is computed over the exact bytes
// Tokenz sent; re-serializing a parsed body can fail verification.
// If your app registers express.json() or another body parser globally,
// mount this webhook route BEFORE it so the raw body is preserved.
app.post("/webhook", express.raw({ type: 'application/json' }), (req, res) => {
  // The signing secret shown once on webhook creation is Base64-encoded.
  const secret = 'webhook-endpoint-secret';
  const sig = req.headers['tokenz-signature'];

  let timestamp, signature;

  for (let sigPart of sig.split(',')) {
    let [key, value] = sigPart.split(':');

    if (key === 't') {
      timestamp = value;
    } else if (key === 'v1') {
      signature = value;
    }
  }

  const checkSignature = crypto.createHmac('sha256', Buffer.from(secret, 'base64'))
    .update(`${timestamp}${req.body.toString('utf8')}`)
    .digest('base64');

  // The `t` value in Tokenz-Signature is a Unix timestamp in milliseconds.
  const tolerance = 5 * 60 * 1000; // 5 minutes

  if (signature === checkSignature && Math.abs(Date.now() - Number(timestamp)) <= tolerance) {
    return res.status(200).end();
  } else {
    return res.status(400).end();
  }
});

4. 웹훅 엔드포인트 테스트

웹훅 처리가 올바르게 동작하는지 확인하려면 테스트 가이드에 따라 테스트 결제를 생성하고 완료하세요.