웹훅
서명된 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 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"
}페이로드 필드
| 필드 | 타입 | 설명 |
|---|---|---|
id | string | 고유 결제 ID |
status | string | 이벤트 발생 시점의 결제 상태 |
txHash | string | null | 온체인 트랜잭션 해시 (미제출 시 null) |
blockNumber | number | null | 트랜잭션이 포함된 블록 번호 (미확인 시 null) |
amount | string | 최소 단위의 금액 (PLM은 wei) |
currency | string | PLM은 'native', ERC20은 컨트랙트 주소 |
orderId | string | null | 결제 생성 시 전달한 내부 주문 ID |
서명 검증
모든 웹훅 요청에는 X-Crypax-Signature 헤더가 포함됩니다. 이벤트를 처리하기 전에 반드시 서명을 검증하여 요청이 Crypax에서 온 것인지 확인하세요.
| 헤더 | 설명 |
|---|---|
X-Crypax-Signature | sha256={원시 바디를 웹훅 시크릿으로 서명한 HMAC-SHA256 hex값} |
X-Crypax-Event | 이벤트 유형, 예: payment.confirmed |
검증 알고리즘
웹훅 시크릿을 키로 원시 요청 바디(JSON 파싱 전 바이트)의 HMAC-SHA256을 계산한 뒤, sha256= 접두사 이후 값과 비교합니다. 타이밍 공격 방지를 위해 crypto.timingSafeEqual을 사용하세요.
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 (직접 검증)
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> 헬퍼를 제공합니다.
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가 다음 일정으로 재전송합니다:
| 시도 | 대기 시간 | 비고 |
|---|---|---|
| 1 | 0s | 최초 전송 |
| 2 | 1s | 1차 재시도 |
| 3 | 5s | 2차 재시도 |
| 4 | 30s | 마지막 재시도 — 이후 추가 시도 없음 |
2xx 응답을 받으면 재시도가 중단됩니다. 각 시도는 대시보드 웹훅 상세 페이지의 전송 로그에 기록됩니다.
권장 사항
- 서명 검증에는 원시 바디를 사용하세요.
express.raw()또는 동등한 미들웨어를 사용하세요. JSON을 먼저 파싱하면 바이트 표현이 달라져 서명이 맞지 않습니다. - 타이밍 안전 비교를 사용하세요. 타이밍 사이드채널 공격 방지를 위해
===대신crypto.timingSafeEqual을 사용하세요. - 핸들러를 멱등하게 만드세요. 동일한 이벤트가 두 번 이상 전달될 수 있습니다.
id필드로 중복을 제거하세요. - 즉시 200으로 응답하세요. DB 쓰기나 다운스트림 호출을 기다리지 마세요. 먼저 응답하고 비동기로 처리하세요.
- HTTPS 엔드포인트만 허용됩니다. Crypax는
https://를 사용하지 않는 웹훅 URL을 거부합니다.