CrypaxDocs

웹훅

서명된 POST 요청으로 실시간 결제 이벤트를 수신하세요. Crypax는 HMAC-SHA256 서명과 함께 웹훅을 전달하므로 처리 전에 신뢰성을 검증할 수 있습니다.


개요

결제 상태가 변경되면 Crypax가 설정된 웹훅 URL로 POST 요청을 보냅니다. 10초 이내에 2xx 상태 코드로 응답해야 합니다. 전송에 실패하면 최대 4회까지 재시도합니다. 웹훅 URL은 대시보드의 웹훅 메뉴에서 설정하세요.

이벤트 종류

Crypax는 아래 이벤트를 발생시킵니다. 개별 이벤트를 구독하거나 비워두면 모든 이벤트를 수신합니다.

이벤트이름발생 시점
payment.confirmed결제 확인온체인에서 트랜잭션이 검증됨. 주문을 처리해도 안전합니다.
payment.failed결제 실패트랜잭션이 제출되었지만 온체인 검증에 실패함.
payment.expired결제 만료expiresAt 이전에 결제가 확인되지 않음.

페이로드 구조

Crypax는 JSON 바디와 함께 두 개의 헤더를 전송합니다: 인증용 X-Crypax-Signature와 이벤트 유형용 X-Crypax-Event.

POST /your-webhook-endpoint
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
X-Crypax-Signature: sha256=a4b3c2d1e0f9...
X-Crypax-Event: payment.confirmed

{
  "id": "pay_01HZ...",
  "status": "confirmed",
  "txHash": "0xabcdef1234...",
  "blockNumber": 12345,
  "amount": "1000000000000000000",
  "currency": "native",
  "orderId": "order_123"
}

페이로드 필드

필드타입설명
idstring고유 결제 ID
statusstring이벤트 발생 시점의 결제 상태
txHashstring | null온체인 트랜잭션 해시 (미제출 시 null)
blockNumbernumber | null트랜잭션이 포함된 블록 번호 (미확인 시 null)
amountstring최소 단위의 금액 (PLM은 wei)
currencystringPLM은 'native', ERC20은 컨트랙트 주소
orderIdstring | null결제 생성 시 전달한 내부 주문 ID

서명 검증

모든 웹훅 요청에는 X-Crypax-Signature 헤더가 포함됩니다. 이벤트를 처리하기 전에 반드시 서명을 검증하여 요청이 Crypax에서 온 것인지 확인하세요.

헤더설명
X-Crypax-Signaturesha256={원시 바디를 웹훅 시크릿으로 서명한 HMAC-SHA256 hex값}
X-Crypax-Event이벤트 유형, 예: payment.confirmed

검증 알고리즘

웹훅 시크릿을 키로 원시 요청 바디(JSON 파싱 전 바이트)의 HMAC-SHA256을 계산한 뒤, sha256= 접두사 이후 값과 비교합니다. 타이밍 공격 방지를 위해 crypto.timingSafeEqual을 사용하세요.

verify-signature.js
const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  // signatureHeader format: "sha256=<hex>"
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

핸들러 예제

웹훅 핸들러는 원시 바디를 파싱하고, 서명을 검증한 뒤, 즉시 200으로 응답해야 합니다. 무거운 작업은 백그라운드 큐로 미루세요.

Express.js (직접 검증)

webhook-handler.js
const express = require('express');
const crypto = require('crypto');

const app = express();

// IMPORTANT: Use raw body parser — JSON.parse changes byte representation
app.post('/webhooks/crypax', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-crypax-signature'];
  const event = req.headers['x-crypax-event'];
  const secret = process.env.CRYPAX_WEBHOOK_SECRET;

  // Verify signature
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(400).json({ error: 'Invalid signature' });
  }

  const payload = JSON.parse(req.body.toString());

  switch (event) {
    case 'payment.confirmed':
      // Fulfill the order
      console.log('Payment confirmed:', payload.id, payload.txHash);
      fulfillOrder(payload.orderId);
      break;
    case 'payment.failed':
      console.log('Payment failed:', payload.id);
      break;
    case 'payment.expired':
      console.log('Payment expired:', payload.id);
      break;
  }

  res.json({ received: true });
});

Express.js (@crypax/node SDK 사용)

Node.js SDK는 서명을 검증하고 타입이 지정된 이벤트 객체를 반환하는 <code>constructEvent</code> 헬퍼를 제공합니다.

webhook-handler-sdk.js
const { Crypax } = require('@crypax/node');
const express = require('express');

const app = express();
const crypax = new Crypax('sk_live_your_secret_key');

app.post('/webhooks/crypax', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-crypax-signature'];

  try {
    const event = crypax.webhooks.constructEvent(
      req.body,
      signature,
      process.env.CRYPAX_WEBHOOK_SECRET
    );

    switch (event.type) {
      case 'payment.confirmed':
        fulfillOrder(event.data.orderId);
        break;
      case 'payment.expired':
        cancelOrder(event.data.orderId);
        break;
    }

    res.json({ received: true });
  } catch (err) {
    res.status(400).json({ error: 'Invalid signature' });
  }
});

재시도 정책

엔드포인트가 10초 이내에 2xx 상태로 응답하지 않으면 Crypax가 다음 일정으로 재전송합니다:

시도대기 시간비고
10s최초 전송
21s1차 재시도
35s2차 재시도
430s마지막 재시도 — 이후 추가 시도 없음

2xx 응답을 받으면 재시도가 중단됩니다. 각 시도는 대시보드 웹훅 상세 페이지의 전송 로그에 기록됩니다.

권장 사항

  • 서명 검증에는 원시 바디를 사용하세요. express.raw() 또는 동등한 미들웨어를 사용하세요. JSON을 먼저 파싱하면 바이트 표현이 달라져 서명이 맞지 않습니다.
  • 타이밍 안전 비교를 사용하세요. 타이밍 사이드채널 공격 방지를 위해 === 대신 crypto.timingSafeEqual을 사용하세요.
  • 핸들러를 멱등하게 만드세요. 동일한 이벤트가 두 번 이상 전달될 수 있습니다. id 필드로 중복을 제거하세요.
  • 즉시 200으로 응답하세요. DB 쓰기나 다운스트림 호출을 기다리지 마세요. 먼저 응답하고 비동기로 처리하세요.
  • HTTPS 엔드포인트만 허용됩니다. Crypax는 https://를 사용하지 않는 웹훅 URL을 거부합니다.

웹훅 가이드 | Crypax