Event Types
| Event | Description |
|---|---|
coupon.redeemed | Coupon redeemed |
payment.created | Payment registered |
sponsor.created | Sponsor created |
Webhook Format
HeadersCopy
Content-Type: application/json
X-Webhook-Batch: true
X-Webhook-Signature: {HMAC-SHA256 signature}
Copy
{
"events": [
{
"event": "payment.created",
"timestamp": "2024-01-15T10:30:00.000Z",
"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:Copy
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 'sponsor.created':
console.log('Sponsor created:', 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:Copy
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.WebhookEventSponsorCreated:
fmt.Println("Sponsor created")
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
Copy
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)
Copy
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) {
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 });
});
Copy
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.Testing Webhooks
Generate test signatures for local development:Copy
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 |
Copy
// 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);
Copy
// 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)