메인 콘텐츠로 건너뛰기
SDK API에서 이벤트 발생 시 게임 서버로 알림을 보냅니다.

이벤트 종류

이벤트설명
coupon.redeemed쿠폰 사용됨
payment.created결제 등록됨
payment.bulk_created벌크 결제 등록됨
sponsor.created부스트 등록됨
sponsor.ended부스트 해제됨

웹훅 형식

헤더
Content-Type: application/json
X-Webhook-Batch: true
X-Webhook-Signature: {HMAC-SHA256 서명}
페이로드
{
  "events": [
    {
      "event": "payment.created",
      "timestamp": "2024-01-15T10:30:00.000Z",
      "callbackId": "my-callback-001",
      "data": {
        "transactionId": "txn_abc123",
        "userId": "user_12345",
        "amount": 9900,
        "currency": "KRW",
        "creatorKey": "ABC12"
      }
    }
  ]
}

서명 검증

웹훅 요청이 PlayCamp에서 온 것인지 확인하기 위해 서명을 검증해야 합니다.
SDK의 verifyWebhook() 유틸리티로 서명 검증, 타임스탬프 검증, 페이로드 파싱을 처리합니다:
import { verifyWebhook } from '@playcamp/node-sdk';

app.post('/webhooks/playcamp', (req, res) => {
  const result = verifyWebhook({
    payload: req.rawBody,
    signature: req.headers['x-webhook-signature'],
    secret: process.env.WEBHOOK_SECRET,
    tolerance: 300,  // 최대 허용 시간 초 (기본값: 300)
  });

  if (!result.valid) {
    return res.status(401).json({ error: result.error });
  }

  for (const event of result.payload.events) {
    switch (event.event) {
      case 'coupon.redeemed':
        console.log('쿠폰 사용:', event.data.couponCode);
        break;
      case 'payment.created':
        console.log('결제 등록:', event.data.transactionId);
        break;
      case 'payment.bulk_created':
        console.log('벌크 결제:', event.data.totalRequested);
        break;
      case 'sponsor.created':
        console.log('부스트 등록:', event.data.userId);
        break;
      case 'sponsor.ended':
        console.log('부스트 해제:', event.data.userId);
        break;
    }
  }

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

수신 예시 (Express)

import { verifyWebhook } from '@playcamp/node-sdk';

app.post('/webhooks/playcamp', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = Buffer.isBuffer(req.body) ? req.body.toString() : req.body;

  const result = verifyWebhook({
    payload,
    signature: req.headers['x-webhook-signature'] as string,
    secret: process.env.WEBHOOK_SECRET!,
  });

  if (!result.valid) {
    return res.status(401).json({ error: result.error });
  }

  for (const event of result.payload.events) {
    console.log('callbackId:', event.callbackId); // 웹훅 추적용 ID
    switch (event.event) {
      case 'coupon.redeemed':
        console.log('쿠폰 사용:', event.data);
        break;
      case 'payment.created':
        console.log('결제 등록:', event.data);
        break;
      case 'payment.bulk_created':
        console.log('벌크 결제:', event.data);
        break;
      case 'sponsor.created':
        console.log('부스트 등록:', event.data);
        break;
      case 'sponsor.ended':
        console.log('부스트 해제:', event.data);
        break;
    }
  }

  res.json({ received: true });
});
express.json() 대신 express.raw({ type: 'application/json' })를 사용해야 원본 body로 서명 검증이 가능합니다.

callbackId 추적

API 요청 시 callbackId를 전달하면, 해당 요청으로 발생한 웹훅 이벤트에 동일한 callbackId가 포함됩니다. 이를 통해 요청과 웹훅 이벤트를 매칭할 수 있습니다.
{
  "events": [
    {
      "event": "payment.created",
      "timestamp": "2026-03-17T12:00:00Z",
      "callbackId": "my-callback-001",
      "data": { "transactionId": "txn_abc123", "userId": "user_12345" }
    }
  ]
}

이벤트별 페이로드

{
  "events": [
    {
      "event": "sponsor.ended",
      "timestamp": "2024-01-15T10:30:00.000Z",
      "callbackId": "game-session-abc123",
      "data": {
        "userId": "user_12345",
        "campaignId": "campaign_001",
        "creatorKey": "ABC12"
      }
    }
  ]
}

payment.bulk_created (벌크 결제)

벌크 결제 완료 시 요약 이벤트가 1회 발생합니다.
{
  "events": [
    {
      "event": "payment.bulk_created",
      "timestamp": "2026-03-17T12:00:00Z",
      "callbackId": "bulk-001",
      "data": {
        "totalRequested": 100,
        "successful": 98,
        "failed": 1,
        "skipped": 1,
        "transactionIds": ["txn_001", "txn_002"]
      }
    }
  ]
}

웹훅 테스트

로컬 개발을 위해 테스트 서명을 생성할 수 있습니다:
import { constructWebhookSignature } from '@playcamp/node-sdk';

const payload = JSON.stringify({
  events: [{
    event: 'coupon.redeemed',
    timestamp: new Date().toISOString(),
    data: { couponCode: 'TEST', userId: 'user_123', usageId: 1, reward: [] },
  }]
});

const signature = constructWebhookSignature(payload, 'your_webhook_secret');

웹훅 관리

기능MethodEndpoint
웹훅 목록GET/v1/server/webhooks
웹훅 등록POST/v1/server/webhooks
웹훅 수정PUT/v1/server/webhooks/:id
웹훅 삭제DELETE/v1/server/webhooks/:id
웹훅 로그GET/v1/server/webhooks/:id/logs
웹훅 테스트POST/v1/server/webhooks/:id/test