Event Types
| Event | Description |
|---|---|
coupon.redeemed | Coupon redeemed |
payment.created | Payment registered |
payment.bulk_created | Bulk payment registered |
sponsor.created | Boost registered |
sponsor.ended | Boost removed |
Webhook Format
HeadersContent-Type: application/json
X-Webhook-Batch: true
X-Webhook-Signature: {HMAC-SHA256 signature}
{
"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"
}
}
]
}
Signature Verification
Verify that webhook requests actually come from PlayCamp.- Node SDK
- Go SDK
- Manual (crypto)
The SDK provides a
verifyWebhook() utility that handles signature verification, timestamp validation, and payload parsing: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, // Max age in seconds (default: 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('Coupon redeemed:', event.data.couponCode);
break;
case 'payment.created':
console.log('Payment created:', event.data.transactionId);
break;
case 'payment.bulk_created':
console.log('Bulk payment:', event.data.totalRequested);
break;
case 'sponsor.created':
console.log('Boost created:', event.data.userId);
break;
case 'sponsor.ended':
console.log('Boost ended:', event.data.userId);
break;
}
}
res.status(200).json({ received: true });
});
The SDK provides the
webhookutil package that handles signature verification, timestamp validation, and payload parsing:import "github.com/playcamp/playcamp-go-sdk/webhookutil"
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"),
Tolerance: 300, // Max age in seconds (default: 300)
})
if !result.Valid {
http.Error(w, result.Error, http.StatusUnauthorized)
return
}
for _, event := range result.Payload.Events {
switch event.Event {
case playcamp.WebhookEventCouponRedeemed:
fmt.Println("Coupon redeemed")
case playcamp.WebhookEventPaymentCreated:
fmt.Println("Payment created")
case playcamp.WebhookEventPaymentBulkCreated:
fmt.Println("Bulk payment")
case playcamp.WebhookEventSponsorCreated:
fmt.Println("Boost created")
case playcamp.WebhookEventSponsorEnded:
fmt.Println("Boost ended")
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string, // raw request body
signature: string, // X-Webhook-Signature header value
secret: string // webhook secret
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Reception Example (Express)
- Node SDK
- Manual (crypto)
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); // webhook tracking ID
switch (event.event) {
case 'coupon.redeemed':
console.log('Coupon redeemed:', event.data);
break;
case 'payment.created':
console.log('Payment created:', event.data);
break;
case 'payment.bulk_created':
console.log('Bulk payment:', event.data);
break;
case 'sponsor.created':
console.log('Boost created:', event.data);
break;
case 'sponsor.ended':
console.log('Boost ended:', event.data);
break;
}
}
res.json({ received: true });
});
import crypto from 'crypto';
app.post('/webhooks/playcamp', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const isBatch = req.headers['x-webhook-batch'] === 'true';
const webhookSecret = process.env.WEBHOOK_SECRET;
const payload = Buffer.isBuffer(req.body)
? req.body.toString()
: req.body;
// 1. Verify signature
if (webhookSecret && signature) {
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
}
// 2. Parse JSON
const data = JSON.parse(payload);
// 3. Process events
const events = isBatch || data.events ? data.events : [data];
for (const event of events) {
switch (event.event) {
case 'coupon.redeemed':
console.log('Coupon redeemed:', event.data);
break;
case 'payment.created':
console.log('Payment created:', event.data);
break;
}
}
res.json({ received: true });
});
Use
express.raw({ type: 'application/json' }) instead of express.json() to enable signature verification with the raw body.callbackId Tracking
When you pass acallbackId with an API request, webhook events triggered by that request will include the same callbackId. This lets you match requests to their webhook events.
{
"events": [
{
"event": "payment.created",
"timestamp": "2026-03-17T12:00:00Z",
"callbackId": "my-callback-001",
"data": { "transactionId": "txn_abc123", "userId": "user_12345" }
}
]
}
Event Payloads
sponsor.ended
{
"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
A summary event triggered once per bulk payment request.{
"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"]
}
}
]
}
Testing Webhooks
Generate test signatures for local development: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');
Webhook Management
- cURL
- Node SDK
- Go SDK
| Function | Method | Endpoint |
|---|---|---|
| List webhooks | GET | /v1/server/webhooks |
| Create webhook | POST | /v1/server/webhooks |
| Update webhook | PUT | /v1/server/webhooks/:id |
| Delete webhook | DELETE | /v1/server/webhooks/:id |
| Get webhook logs | GET | /v1/server/webhooks/:id/logs |
| Test webhook | POST | /v1/server/webhooks/:id/test |
// Create a webhook
const webhook = await server.webhooks.create({
eventType: 'coupon.redeemed',
url: 'https://your-server.com/webhooks/playcamp',
});
// List webhooks
const webhooks = await server.webhooks.listWebhooks();
// Update a webhook
await server.webhooks.update(webhook.id, {
url: 'https://your-server.com/webhooks/v2',
});
// Delete a webhook
await server.webhooks.remove(webhook.id);
// Get webhook delivery logs
const logs = await server.webhooks.getLogs(webhook.id);
// Send a test event
await server.webhooks.test(webhook.id);
// Create a webhook
webhook, err := server.Webhooks.Create(ctx, playcamp.CreateWebhookParams{
EventType: playcamp.WebhookEventCouponRedeemed,
URL: "https://your-server.com/webhooks/playcamp",
})
// List webhooks
webhooks, err := server.Webhooks.List(ctx)
// Update a webhook
updated, err := server.Webhooks.Update(ctx, webhook.ID, playcamp.UpdateWebhookParams{
URL: playcamp.String("https://your-server.com/webhooks/v2"),
})
// Delete a webhook
err := server.Webhooks.Delete(ctx, webhook.ID)
// Get webhook delivery logs
logs, err := server.Webhooks.GetLogs(ctx, webhook.ID)
// Send a test event
result, err := server.Webhooks.Test(ctx, webhook.ID)