Handle Webhook
This guide shows how to receive, validate, and process webhook events using your prefered language
Requirements
An HTTPS endpoint that accepts
POST
PHP, Python, NodeJS, Java...
Secure & Handle Webhook
When your server receives a webhook, you should verify the signature before processing it. This ensures the request really comes from the platform and not from someone else.
Example payload (short)
Here is an example of the data you will receive, it contains all the necessary data related to the payment: user identifiers, basket contents, price, payment ID...
{
"mode": "live",
"event": "payment.success",
"created_at": "2025-09-04T13:45:44-04:00",
"request_id": "51b97ba5891ec220e8b64385a00c3826",
"webhook_id": "458",
"store_id": "7541",
"data": {
"id": 71134,
"transaction_id": "68B9D0471D02A",
"gateway": "paypal",
"amount": { "total_paid": 12, "currency": "EUR" },
"user": { "email": "[email protected]", "discord_id": "123", "username": "Player" },
"basket": [{ "id": 183, "name": "VIP Rank", "price": 10, "quantity": 1 }],
"actions": { "...": "..." }
}
}
Create an endpoint
Create a file in your chosen language and start a server that listens for POST requests using the following examples:
<?php
// Secret key (Tip4serv Dashboard -> Webhook -> Custom Webhook)
$webhookSecretBase64 = "y2x3...==";
// 1. Read the raw request body (JSON string)
$requestBody = file_get_contents('php://input');
// 2. Read headers sent by the platform
$timestampHeader = $_SERVER['HTTP_X_PAY_TIMESTAMP'] ?? '';
$signatureHeader = $_SERVER['HTTP_X_PAY_SIGNATURE'] ?? '';
// 3. Reject if the webhook is too old (max 5 minutes)
if (abs(time() - intval($timestampHeader)) > 300) {
http_response_code(401);
exit("invalid timestamp");
}
// 4. Recalculate the expected signature
$baseString = $timestampHeader . "." . $requestBody;
$secretBinary = base64_decode($webhookSecretBase64);
$expectedSignature = hash_hmac('sha256', $baseString, $secretBinary);
// 5. Compare signatures
if (!hash_equals($expectedSignature, $signatureHeader)) {
http_response_code(401);
exit("invalid signature");
}
// 6. If we’re here, the webhook is authentic
$event = json_decode($requestBody, true);
// Example: handle payment update
if ($event['event'] === 'payment.success') {
$paymentId = $event['data']['id'];
$discord_id = $event['data']['user']['discord_id'];
// TODO: update your database here
}
http_response_code(200);
How it works
Read the body → the raw JSON payload sent by the platform.
Get headers →
X-Pay-Timestamp
andX-Pay-Signature
(provided in every request).Check freshness → reject if older than 5 minutes.
Recalculate signature →
HMAC-SHA256(timestamp.body, secret)
.Compare signatures → use
hash_equals
to prevent timing attacks.Process event → decode JSON and handle it (e.g., update DB).
Tips & best practices
Respond fast (under a few seconds). Offload heavy work to a queue/worker.
Treat optional fields defensively: not every
user
identifier orcustom_fields
will be present.Log the raw payload (with privacy in mind) to help debugging.
Last updated