PlayCamp가 제공하는 WebView UI를 게임에 임베드하여 크리에이터 부스트 기능을 제공합니다. 별도 UI 개발 없이 가장 빠르게 연동할 수 있는 방식입니다.
이 방식이 적합한 경우
- 빠르게 크리에이터 부스트 기능을 제공하고 싶을 때
- 별도 UI 개발 없이 연동하고 싶을 때
- PlayCamp가 제공하는 크리에이터 검색/선택 UI를 그대로 사용해도 될 때
전체 흐름
게임사 개발 작업
| # | 작업 | 담당 | 설명 |
|---|
| 1 | PlayCamp 진입점 배치 | 게임 클라이언트 | 게임 내 아이콘/버튼 배치 |
| 2 | OTT 발급 엔드포인트 | 게임 서버 | 클라이언트 요청 시 OTT 발급 후 전달 |
| 3 | WebView 호출 | 게임 클라이언트 | OTT로 WebView URL 열기 |
| 4 | 웹훅 수신 엔드포인트 | 게임 서버 | 부스트 매칭 결과 수신 |
| 5 | 결제 등록 API 연동 | 게임 서버 | 인게임 결제 발생 시 PlayCamp로 전송 |
Step 1: OTT 발급 (게임 서버)
게임 서버에서 PlayCamp SDK API로 OTT(One-Time Token)를 발급합니다. OTT는 1회용, 60초 TTL이므로 유저가 WebView를 열 때마다 새로 발급해야 합니다.
curl -X POST "https://sandbox-sdk-api.playcamp.io/v1/server/webview/ott" \
-H "Authorization: Bearer ak_server_xxx:secret" \
-H "Content-Type: application/json" \
-d '{
"userId": "게임_유저_고유번호"
}'
const result = await server.webview.createOtt({
userId: '게임_유저_고유번호',
});
// result.ott → OTT 토큰 (클라이언트에 전달)
result, err := server.Webview.CreateOTT(ctx, playcamp.WebviewOttParams{
UserID: "게임_유저_고유번호",
})
// result.OTT → OTT 토큰 (클라이언트에 전달)
응답
{
"data": {
"ott": "a1b2c3d4e5f6...64자hex",
"expiresIn": 60
}
}
OTT는 반드시 게임 서버에서 발급해야 합니다. 클라이언트에서 직접 발급하면 Server Key가 노출됩니다.
상세: WebView 연동 - OTT 발급 API
Step 2: WebView 호출 (게임 클라이언트)
게임 서버로부터 받은 OTT로 WebView URL을 열어줍니다.
기본 URL
| 환경 | URL |
|---|
| Sandbox | https://sandbox-sdk-api.playcamp.io/webview/?ott={토큰} |
| Live | https://sdk-api.playcamp.io/webview/?ott={토큰} |
옵션 파라미터
| 파라미터 | 설명 | 예시 |
|---|
lang | 언어 (ko, en) | &lang=ko |
tabs | 표시할 탭 제한 | &tabs=sponsor |
primaryColor | 테마 색상 (hex, # 없이) | &primaryColor=FF6B35 |
전체 예시
https://sandbox-sdk-api.playcamp.io/webview/?ott=a1b2c3d4...&lang=ko&tabs=sponsor&primaryColor=FF6B35
WebView는 게임 내 임베디드 브라우저, 외부 브라우저(모바일/PC) 모두 지원됩니다. 게임 환경에 맞게 선택하세요.
상세: WebView 연동 - URL 구성
Step 3: 웹훅으로 부스트 매칭 수신 (게임 서버)
유저가 WebView에서 크리에이터를 선택하면, PlayCamp가 게임 서버의 웹훅 URL로 매칭 결과를 전송합니다.
{
"events": [
{
"event": "sponsor.created",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"userId": "게임_유저_고유번호",
"campaignId": "campaign_001",
"creatorKey": "ABC12"
}
}
]
}
| 필드 | 설명 |
|---|
userId | 게임 유저 고유번호 (OTT 발급 시 전달한 값) |
campaignId | 캠페인 ID |
creatorKey | 유저가 선택한 크리에이터 식별자 |
서명 검증
웹훅 헤더의 X-Webhook-Signature로 요청이 PlayCamp에서 온 것인지 검증합니다.
import { verifyWebhook } from '@playcamp/node-sdk';
app.post('/webhooks/playcamp', express.raw({ type: 'application/json' }), (req, res) => {
const result = verifyWebhook({
payload: req.body.toString(),
signature: req.headers['x-webhook-signature'],
secret: process.env.WEBHOOK_SECRET,
});
if (!result.valid) {
return res.status(401).json({ error: result.error });
}
for (const event of result.payload.events) {
if (event.event === 'sponsor.created') {
// userId와 creatorKey 매칭 정보 저장
console.log(`유저 ${event.data.userId} → 크리에이터 ${event.data.creatorKey}`);
}
}
res.json({ received: true });
});
func handleWebhook(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
result := webhookutil.Verify(webhookutil.VerifyOptions{
Payload: body,
Signature: r.Header.Get("X-Webhook-Signature"),
Secret: os.Getenv("WEBHOOK_SECRET"),
})
if !result.Valid {
http.Error(w, result.Error, http.StatusUnauthorized)
return
}
for _, event := range result.Payload.Events {
if event.Event == playcamp.WebhookEventSponsorCreated {
// userId와 creatorKey 매칭 정보 저장
fmt.Printf("유저 %s → 크리에이터 %s\n", event.Data.UserID, event.Data.CreatorKey)
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
부스트 해제 시에는 sponsor.ended 이벤트가 발생합니다. 동일한 데이터 구조(userId, campaignId, creatorKey)로 전달됩니다.
상세: 웹훅 이벤트
Step 4: 결제 등록 (게임 서버)
인게임 결제가 발생하면 게임 서버에서 PlayCamp로 결제 정보를 전송합니다. 등록된 결제는 유저가 부스트 중인 크리에이터에게 자동 귀속됩니다.
curl -X POST "https://sandbox-sdk-api.playcamp.io/v1/server/payments" \
-H "Authorization: Bearer ak_server_xxx:secret" \
-H "Content-Type: application/json" \
-d '{
"userId": "게임_유저_고유번호",
"transactionId": "txn_abc123",
"productId": "gem_pack_100",
"productName": "100 보석 팩",
"amount": 9900,
"currency": "KRW",
"platform": "Android",
"distributionType": "MOBILE_STORE",
"purchasedAt": "2024-01-15T10:30:00.000Z"
}'
const payment = await server.payments.create({
userId: '게임_유저_고유번호',
transactionId: 'txn_abc123',
productId: 'gem_pack_100',
productName: '100 보석 팩',
amount: 9900,
currency: 'KRW',
platform: 'Android',
distributionType: 'MOBILE_STORE',
purchasedAt: new Date('2024-01-15T10:30:00Z'),
});
purchasedAt, _ := time.Parse(time.RFC3339, "2024-01-15T10:30:00Z")
payment, err := server.Payments.Create(ctx, playcamp.CreatePaymentParams{
UserID: "게임_유저_고유번호",
TransactionID: "txn_abc123",
ProductID: "gem_pack_100",
ProductName: playcamp.String("100 보석 팩"),
Amount: 9900,
Currency: "KRW",
Platform: playcamp.PaymentPlatformAndroid,
DistributionType: playcamp.String("MOBILE_STORE"),
PurchasedAt: purchasedAt,
})
결제는 건별로 등록하거나, 최대 1,000건을 한번에 등록(벌크)할 수 있습니다. 환불 시에는 POST /payments/{transactionId}/refund를 호출합니다.
상세: 결제 등록
Step 5: 정산
결제 데이터를 기반으로 월 단위 정산이 진행됩니다.
- 매출 마감 — 매월 말 기준으로 결제 데이터 집계
- 매출 대사 — PlayCamp에서 제공하는 정산 내역과 게임사 내부 데이터 대사
- 정산 입금 — 대사 확인 후 정산 금액 입금
- 크리에이터 정산 — PlayCamp가 크리에이터에게 수익 분배
상세: 정산