Webhook 가이드
FakeToss는 결제 상태, 가상계좌 입금, 취소 상태를 웹훅으로 전송합니다. 페이로드의 `secret` 값을 저장된 웹훅 시크릿과 비교해 검증하세요.
Payload format
{
"eventType": "PAYMENT_STATUS_CHANGED",
"createdAt": "2026-04-21T10:20:00+09:00",
"secret": "whsec_live_or_test_value",
"data": { ... }
}PAYMENT_STATUS_CHANGED
{
"eventType": "PAYMENT_STATUS_CHANGED",
"createdAt": "2026-04-21T10:20:00+09:00",
"secret": "whsec_123",
"data": {
"paymentKey": "pay_123",
"orderId": "order-001",
"status": "DONE",
"approvedAt": "2026-04-21T10:19:58+09:00"
}
}DEPOSIT_CALLBACK
{
"eventType": "DEPOSIT_CALLBACK",
"createdAt": "2026-04-21T10:30:00+09:00",
"secret": "whsec_123",
"data": {
"paymentKey": "pay_va_123",
"accountNumber": "140-123-456789",
"depositedAmount": 25000,
"status": "DONE"
}
}CANCEL_STATUS_CHANGED
{
"eventType": "CANCEL_STATUS_CHANGED",
"createdAt": "2026-04-21T11:00:00+09:00",
"secret": "whsec_123",
"data": {
"paymentKey": "pay_123",
"status": "CANCELED",
"cancelAmount": 15000,
"reason": "고객 요청"
}
}Signature verification
HMAC 서명이 아니라 본문 안의 `secret` 필드를 검증합니다. 문자열 비교는 `timingSafeEqual`을 사용해 길이와 값을 함께 확인하세요.
import { NextRequest, NextResponse } from "next/server";
import { timingSafeEqual } from "node:crypto";
function safeEqual(a: string, b: string) {
const left = Buffer.from(a);
const right = Buffer.from(b);
return left.length === right.length && timingSafeEqual(left, right);
}
export async function POST(request: NextRequest) {
const payload = await request.json();
const webhookSecret = process.env.FAKETOSS_WEBHOOK_SECRET || "";
if (!safeEqual(payload.secret || "", webhookSecret)) {
return NextResponse.json({ code: "INVALID_SECRET" }, { status: 401 });
}
return NextResponse.json({ received: true });
}Retry schedule
전송 실패 시 1분, 4분, 16분, 64분, 256분 간격으로 재시도하며 최대 5회까지 보냅니다.
수동 발송
관리자 대시보드에서 개별 결제 이벤트를 직접 다시 보낼 수 있습니다. 운영 점검이나 리시버 디버깅 시 /admin/webhooks의 `Fire webhook` 버튼을 사용하세요.