Payment Callback (Webhook)
Receive real-time notifications when payment status changes. Webhooks are the primary way to stay updated on order status and automatically fulfill customer orders.
Overview
MugglePay sends webhook events to notify your application whenever an order status changes. This is the ONLY backend mechanism you need to track payment status and automatically fulfill orders.
Use Cases
Webhooks enable you to:
Update customer records when subscription payments succeed
Send confirmation emails after successful payments
Log accounting entries when transfers are completed
Update inventory when orders are paid
Activate services immediately upon payment confirmation
Webhook Configuration
Setting Up Your Endpoint
Provide callback URL in your Create Order request
Ensure endpoint is public and accessible from the internet
Handle POST requests with JSON payloads
Return HTTP 200 to acknowledge receipt
Authentication
Webhooks are authenticated using the merchant_token
field you provide in the Create Order request. This prevents fraudulent callback attempts.
Request Details
HTTP Method
Method: POST
Content-Type: application/json
Authentication: Token-based (from Create Order)
Response Requirement
Your webhook endpoint must return HTTP 200 with this JSON response:
{
"status": 200
}
Webhook Payload Structure
order_id
string
MugglePay's internal order identifier
merchant_order_id
string
Your custom order ID from Create Order
status
string
Current payment status (NEW, PENDING, PAID, etc.)
price_amount
string
Original price set by merchant (e.g., "29.99")
price_currency
string
Currency for pricing (e.g., "USD")
pay_amount
string
Actual amount paid by customer
pay_currency
string
Currency used for payment
created_at
string
ISO 8601 timestamp of order creation
created_at_t
number
Unix timestamp (epoch) of order creation
merchant_token
string
Your custom merchant_token for webhook validation
meta
object
Additional payment information (optional)
Code Examples
Node.js/Express Example
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/payment', async (req, res) => {
try {
const {
order_id,
merchant_order_id,
status,
price_amount,
price_currency,
merchant_token
} = req.body;
// Validate webhook token
if (!validateWebhookToken(merchant_token)) {
console.error('Invalid webhook merchant_token');
return res.status(401).json({ error: 'Unauthorized' });
}
// Handle different status changes
switch (status) {
case 'PAID':
await fulfillOrder(merchant_order_id);
console.log(`Order ${merchant_order_id} paid successfully`);
break;
case 'EXPIRED':
await handleExpiredOrder(merchant_order_id);
console.log(`Order ${merchant_order_id} expired`);
break;
case 'CANCELED':
await handleCanceledOrder(merchant_order_id);
console.log(`Order ${merchant_order_id} canceled`);
break;
default:
console.log(`Order ${merchant_order_id} status: ${status}`);
}
// Always return 200 to acknowledge receipt
res.json({ status: 200 });
} catch (error) {
console.error('Webhook processing error:', error);
// Still return 200 to prevent retries
res.json({ status: 200 });
}
});
function validateWebhookToken(merchant_token) {
// Implement your token validation logic
return merchant_token === process.env.WEBHOOK_TOKEN;
}
async function fulfillOrder(merchantOrderId) {
// Implement order fulfillment logic
// Update database, send confirmation email, etc.
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
Python/Flask Example
from flask import Flask, request, jsonify
import os
app = Flask(__name__)
@app.route('/webhooks/payment', methods=['POST'])
def payment_webhook():
try:
data = request.get_json()
# Extract webhook data
order_id = data.get('order_id')
merchant_order_id = data.get('merchant_order_id')
status = data.get('status')
token = data.get('token')
# Validate webhook token
if not validate_webhook_token(token):
print(f"Invalid webhook token for order {merchant_order_id}")
return jsonify({'error': 'Unauthorized'}), 401
# Process status change
if status == 'PAID':
fulfill_order(merchant_order_id)
print(f"Order {merchant_order_id} fulfilled successfully")
elif status == 'EXPIRED':
handle_expired_order(merchant_order_id)
print(f"Order {merchant_order_id} expired")
elif status == 'CANCELED':
handle_canceled_order(merchant_order_id)
print(f"Order {merchant_order_id} canceled")
# Return success response
return jsonify({'status': 200})
except Exception as e:
print(f"Webhook processing error: {e}")
# Return 200 even on error to prevent retries
return jsonify({'status': 200})
def validate_webhook_token(token):
return token == os.environ.get('WEBHOOK_TOKEN')
def fulfill_order(merchant_order_id):
# Implement order fulfillment logic
pass
if __name__ == '__main__':
app.run(debug=True, port=3000)
PHP Example
<?php
// webhook.php
// Get webhook data
$webhook_data = json_decode(file_get_contents('php://input'), true);
// Validate webhook token
$expected_token = $_ENV['WEBHOOK_TOKEN'];
$received_token = $webhook_data['token'] ?? '';
if ($received_token !== $expected_token) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// Extract webhook fields
$order_id = $webhook_data['order_id'];
$merchant_order_id = $webhook_data['merchant_order_id'];
$status = $webhook_data['status'];
$price_amount = $webhook_data['price_amount'];
$price_currency = $webhook_data['price_currency'];
// Process status change
switch ($status) {
case 'PAID':
fulfillOrder($merchant_order_id);
error_log("Order {$merchant_order_id} paid successfully");
break;
case 'EXPIRED':
handleExpiredOrder($merchant_order_id);
error_log("Order {$merchant_order_id} expired");
break;
case 'CANCELED':
handleCanceledOrder($merchant_order_id);
error_log("Order {$merchant_order_id} canceled");
break;
default:
error_log("Order {$merchant_order_id} status: {$status}");
}
// Return success response
http_response_code(200);
echo json_encode(['status' => 200]);
function fulfillOrder($merchantOrderId) {
// Implement order fulfillment logic
// Update database, send confirmation email, etc.
}
function handleExpiredOrder($merchantOrderId) {
// Handle expired order logic
}
function handleCanceledOrder($merchantOrderId) {
// Handle canceled order logic
}
?>
Sample Webhook Payload
{
"merchant_order_id": "order_12345",
"order_id": "94be2b2a-2905-4857-b701-b04e57e84593",
"status": "PAID",
"price_amount": "29.99",
"price_currency": "USD",
"pay_amount": "29.99",
"pay_currency": "USD",
"created_at": "2024-01-15T10:30:00.000Z",
"created_at_t": 1705312200000,
"token": "your_custom_token_123",
"meta": {
"payment": "USDT_ARB",
"transaction_hash": "0x1234...",
"network": "Arbitrum"
}
}
Retry Mechanism
MugglePay automatically retries failed webhook deliveries using exponential backoff:
1st
1 minute
1 minute
2nd
2 minutes
3 minutes
3rd
4 minutes
7 minutes
4th
8 minutes
15 minutes
5th
16 minutes
31 minutes
6th
32 minutes
63 minutes
7th
64 minutes
2 hours 7 minutes
8th
128 minutes
4 hours 15 minutes
9th
256 minutes
8 hours 31 minutes
Continues
Doubles
Up to 3 days
Important: Always return HTTP 200 to prevent unnecessary retries.
Security Best Practices
✅ Do's
Validate webhook tokens to prevent fraud
Use HTTPS for all webhook endpoints
Implement idempotency to handle duplicate webhooks
Log all webhook events for debugging
Return HTTP 200 even on processing errors
❌ Don'ts
Don't ignore webhook validation
Don't expose webhook endpoints without authentication
Don't return error codes that trigger retries
Don't process webhooks synchronously for long operations
Testing Webhooks
Local Development
Use tools like ngrok to expose local endpoints
Test with small amounts in sandbox mode
Verify webhook delivery and processing
Manual Testing
Use the Merchant Portal
Click "Trigger Payment Callback" button for testing
Monitor webhook delivery in your logs
Troubleshooting
Common Issues
Webhook Not Receiving
Check endpoint accessibility: Ensure your URL is publicly accessible
Verify callback_url: Confirm it's correctly set in Create Order
Check firewall settings: Ensure incoming POST requests are allowed
Test manually: Use Merchant Portal to trigger test callbacks
Webhook Processing Errors
Validate merchant token: Ensure webhook merchant_token matches your expected value
Check payload structure: Verify all required fields are present
Handle errors gracefully: Return 200 even when processing fails
Monitor logs: Check for specific error messages
Duplicate Webhooks
Implement idempotency: Process each webhook only once
Check order_id: Use unique identifiers to prevent duplicates
Database constraints: Add unique constraints on order processing
Related Documentation
Create Order - Set up webhook endpoints
Order Status - Understand status values
Authentication - Secure your webhooks
Getting Started - Complete integration guide
Next Steps
Implement webhook endpoint following the examples above
Test webhook delivery with small test orders
Add order fulfillment logic for PAID status
Monitor webhook processing in production
Set up alerting for webhook failures
Last updated