Webhook Events
Documentation for webhook events sent to your endpoints when payment status changes
Overview
When you include a webhook_endpoint
parameter during payment creation, TuxoPay will send webhook notifications to your endpoint when payment status changes.
All webhooks are signed with HMAC-SHA256 for security. Always verify the signature before processing webhook data.
Receiving Webhooks
TuxoPay will POST webhook notifications to your specified endpoint with the following structure:
Endpoint
POST https://your-domain.com/webhooks/tuxopay
Headers
Content-Type: application/json
Signature: {hmac_sha256_signature}
Webhook Payload Structure
{
"event": "payment.status_changed",
"timestamp": "2025-06-30T18:56:32Z",
"payment": {
"id": 123,
"payment_session_id": "sess_abc123",
"provider_transaction_id": "txn_xyz789",
"amount": 100.00,
"currency": "USD",
"status": "completed",
"country": "US",
"payment_provider": "blackpay",
"flow_id": 1,
"created_at": "2025-06-30T18:50:00Z",
"updated_at": "2025-06-30T18:56:32Z",
"expires_at": "2025-06-30T19:50:00Z"
},
"customer": {
"id": 456,
"email": "[email protected]",
"phone": "+1234567890",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"address": "123 Main St",
"city": "New York",
"region": "NY",
"postal_code": "10001",
"country_code": "US",
"date_of_birth": "1990-01-01",
"gender": "male",
"company": "Acme Corp",
"vat_number": "US123456789"
},
"shop": {
"id": 789,
"name": "My Shop"
},
"metadata": {
"order_id": "ORDER-123",
"custom_field": "custom_value"
},
"provider_metadata": {},
"original_webhook": {
"provider": "blackpay",
"data": {
"order_token": "ord_abc123",
"siteb_status": "completed"
},
"processed_at": "2025-06-30T18:56:32Z"
}
}
Event Types
payment.status_changed
Sent whenever a payment status changes. Possible status values:
pending
- Payment initiated but not yet processedprocessing
- Payment is being processedcompleted
- Payment successfully completedfailed
- Payment failedcancelled
- Payment was cancelledrefunded
- Payment was refunded
Payload Fields
Webhook Security
Always verify webhook signatures to ensure requests are from TuxoPay and haven't been tampered with.
All webhooks include a Signature
header containing an HMAC-SHA256 signature of the request body. Verify it using your shop's webhook secret.
Signature Verification
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_SIGNATURE'] ?? '';
$webhookSecret = 'your-shop-webhook-secret';
$expectedSignature = hash_hmac('sha256', $payload, $webhookSecret);
if (!hash_equals($signature, $expectedSignature)) {
http_response_code(401);
exit('Invalid signature');
}
// Signature is valid, process the webhook
$data = json_decode($payload, true);
const crypto = require('crypto');
function verifyWebhook(req, webhookSecret) {
const signature = req.headers['signature'];
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex');
if (signature !== expectedSignature) {
throw new Error('Invalid signature');
}
return true;
}
// Usage in Express
app.post('/webhooks/tuxopay', (req, res) => {
try {
verifyWebhook(req, process.env.WEBHOOK_SECRET);
// Process webhook
const { event, payment, customer } = req.body;
res.json({ success: true });
} catch (error) {
res.status(401).json({ error: 'Invalid signature' });
}
});
import hmac
import hashlib
def verify_webhook(payload, signature, webhook_secret):
expected_signature = hmac.new(
webhook_secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Usage in Flask
@app.route('/webhooks/tuxopay', methods=['POST'])
def handle_webhook():
signature = request.headers.get('Signature')
payload = request.get_data(as_text=True)
if not verify_webhook(payload, signature, WEBHOOK_SECRET):
return {'error': 'Invalid signature'}, 401
data = request.json
# Process webhook
return {'success': True}
Retry Logic
TuxoPay automatically retries failed webhook deliveries with exponential backoff.
Attempt | Delay | Timeout |
---|---|---|
1st | Immediate | 3 seconds |
2nd | 10 second | 3 seconds |
3rd | 100 seconds | 3 seconds |
4th | 1000 seconds | 3 seconds |
Maximum retries: 3 attempts
Examples
Completed Payment
{
"event": "payment.status_changed",
"timestamp": "2025-06-30T18:56:32Z",
"payment": {
"id": 123,
"payment_session_id": "sess_abc123",
"provider_transaction_id": "txn_xyz789",
"amount": 100.00,
"currency": "USD",
"status": "completed",
"country": "US",
"payment_provider": "blackpay",
"flow_id": 1
},
"customer": {
"id": 456,
"email": "[email protected]",
"first_name": "John",
"last_name": "Doe"
},
"shop": {
"id": 789,
"name": "My Shop"
}
}
Failed Payment
{
"event": "payment.status_changed",
"timestamp": "2025-06-30T18:58:15Z",
"payment": {
"id": 124,
"payment_session_id": "sess_def456",
"provider_transaction_id": "txn_fail999",
"amount": 50.00,
"currency": "EUR",
"status": "failed",
"country": "DE",
"payment_provider": "stripe",
"flow_id": 2
},
"customer": {
"id": 457,
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Smith"
},
"shop": {
"id": 789,
"name": "My Shop"
}
}
Best Practices
- Respond Quickly - Return a 2xx status code within 5 seconds to acknowledge receipt
- Process Asynchronously - Queue webhooks for background processing to avoid timeouts
- Handle Duplicates - Implement idempotency using the
payment.id
to handle duplicate deliveries - Log Everything - Log all webhook receipts for debugging and audit purposes
- Verify Signatures - Always verify the signature before processing webhook data
Testing Your Webhook Endpoint
Use the TuxoPay API to test your webhook endpoint:
curl -X POST https://orchestrator.tuxopay.com/api/webhooks/shop/{shopId}/test \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook_endpoint": "https://your-shop.com/webhooks/test"
}'
Getting Webhook Statistics
Monitor your webhook delivery success rate:
curl -X GET https://orchestrator.tuxopay.com/api/webhooks/shop/{shopId}/stats \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"success": true,
"data": {
"shop_id": 789,
"shop_name": "My Shop",
"total_payments": 150,
"payments_with_webhooks": 148,
"webhook_coverage_percentage": 98.67,
"recent_webhooks": [],
"webhook_secret": "your-webhook-secret"
}
}