Webhooks
What is a Webhook?
Webhook (web hook) is a mechanism that allows Mesilat platform to automatically notify your server about important events in real time. When an event occurs (for example, successful payment), the platform sends an HTTP POST request to your specified URL with event data.
Webhook Data
When sending webhook, the following data is transmitted in request body:
- additional_fields — additional fields that were configured in product settings and filled by buyer on checkout page
- metadata — metadata that was passed when creating payment link (only for payment links)
- Information about order, buyer and payment
Webhook Data Example
{
"uuid": "b9c3e992",
"amount": 500,
"total_amount": 460,
"currency": "RUB",
"commission": 40,
"exchange_rate": 1,
"created_at": 1759919382,
"metadata": [],
"additional_fields": {
"hidden_0": {
"text": "platform_product_id=115123584",
"type": "hidden",
"value": null
}
},
"customer_phone": "+35796612353",
"customer_name": "Emma S.",
"customer_email": "[email protected]",
"product_id": 141,
"product_title": "Custom Service",
"product_type": "CustomService",
"trigger_type": "auto"
}
Field Description
Transaction Information
| Field | Type | Description |
|---|---|---|
uuid |
string | Unique transaction identifier. Use for ensuring idempotency |
amount |
number | Full payment amount in main currency units (e.g., 20 EUR) |
total_amount |
number | Final amount after platform commission deduction |
commission |
number | Platform commission amount deducted from transaction |
currency |
string | Payment currency. Possible values: EUR, USD, RUB |
exchange_rate |
number | Currency exchange rate (if conversion is applied) |
created_at |
integer | Transaction creation time in Unix timestamp format |
Customer Data
| Field | Type | Description |
|---|---|---|
customer_name |
string | Customer name specified during order placement |
customer_email |
string | Customer email |
customer_phone |
string | Customer phone in international format |
Product Information
| Field | Type | Description |
|---|---|---|
product_id |
integer | Unique product identifier |
product_title |
string | Product title |
product_type |
string | Product type (see table below) |
Additional Data
| Field | Type | Description |
|---|---|---|
additional_fields |
object/array | Additional fields configured in product and filled by buyer on checkout. See detailed structure description below |
metadata |
object/array | Metadata passed when creating payment link. Available only for payment links. May contain any user data or be empty array [] |
trigger_type |
string | Webhook trigger type: auto (automatic on successful payment) or manual (test event) |
Creating additional_fields
Additional fields are configured in Mesilat platform interface in "Customer Information" section of specific product:

In the interface you can:
- Create additional fields of various types
- Configure which fields are required for filling
All fields filled by customer during order placement will be passed in webhook in additional_fields section.
additional_fields Format
additional_fields field can have two formats:
1. Empty array (if additional fields are not configured):
2. Object with fields (if additional fields are configured and filled):
"additional_fields": {
"field_name": {
"text": "field value or description",
"type": "field type (text, email, phone, hidden, etc.)",
"value": "field value or null"
}
}
Example with filled fields:
"additional_fields": {
"hidden_0": {
"text": "platform_product_id=115123584",
"type": "hidden",
"value": null
},
"text_1": {
"text": "Additional information",
"type": "text",
"value": "Additional information"
},
"checkbox_2": {
"text": "I agree to terms",
"type": "checkbox",
"value": "true"
},
"select_3": {
"text": "Premium",
"type": "select",
"value": "Premium"
}
}
Each field in additional_fields contains:
- text — text value or field description
- type — field type (see table of possible types below)
- value — filled field value or null
Possible additional_fields Types
| Type | Description |
|---|---|
text |
Text field |
checkbox |
Checkbox (tick box) |
radio |
Radio button (single choice) |
select |
Dropdown list |
link |
Link/URL field |
hidden |
Hidden field |
Product Types (product_type)
| Value | Description |
|---|---|
DigitalDownload |
Digital product for download (files, documents, media) |
CustomService |
Individual service |
RecurringMembership |
Subscription with recurring payments |
Webhook Security
Webhook Key
Each webhook is signed with special webhook_key that is passed in request header webhook-key. This allows you to verify that request actually came from Mesilat platform.
Request header:
Signature verification example:
// PHP
$webhookKey = $_SERVER['HTTP_WEBHOOK_KEY'] ?? '';
$expectedKey = getenv('MESILAT_WEBHOOK_KEY');
if ($webhookKey !== $expectedKey) {
http_response_code(401);
exit('Invalid webhook key');
}
// Node.js
const webhookKey = req.headers['webhook-key'];
const expectedKey = process.env.MESILAT_WEBHOOK_KEY;
if (webhookKey !== expectedKey) {
return res.status(401).send('Invalid webhook key');
}
# Python
webhook_key = request.headers.get('webhook-key')
expected_key = os.getenv('MESILAT_WEBHOOK_KEY')
if webhook_key != expected_key:
return Response('Invalid webhook key', status=401)
Webhook Configuration
Webhook is configured in Mesilat platform personal cabinet:

Configuration Steps:
- Enable webhook — activate webhook notification sending function
- Specify your webhook URL — enter full URL address of your server that will receive notifications (e.g.:
https://webhook-test.com/a6f72e919ce3f0c26f4a12cfa45c9a7a) - Generate and save webhook key — create unique key for request signing and save it in secure place
- Click "Save" — apply settings
- Send test event to your webhook — check integration functionality by sending test request
Retry Attempts
Mesilat platform automatically retries webhook sending in case of failure (if your server didn't return 200 response code).
Retry Mechanism:
Total of up to 10 attempts with increasing intervals:
| Attempt | Interval |
|---|---|
| 1 | Immediately |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 10 minutes |
| 5 | 20 minutes |
| 6 | 30 minutes |
| 7 | 1 hour |
| 8 | 1 hour |
| 9 | 1 hour |
| 10 | 1 hour |
Successful Delivery Conditions:
✅ Your server should return HTTP response code 200 to confirm webhook receipt
❌ Any other codes (4xx, 5xx) or timeout will be considered failed attempt
Failure Notification
If after 10 attempts webhook still wasn't delivered, notification email will be sent with information about the problem. This allows you to quickly respond to integration failure.
Webhook Processing Requirements
Fast Response
Your endpoint should respond quickly (within 5-10 seconds). If data processing requires time, use asynchronous processing:
- Receive webhook
- Save data to queue/database
- Immediately return code 200
- Process data in background
Idempotency
Webhook may be delivered multiple times (on retry attempts). Ensure that repeated processing of same event doesn't lead to action duplication. Use transaction uuid for uniqueness check.
Handler Example
// PHP (Laravel)
Route::post('/webhooks/mesilat', function (Request $request) {
// Webhook key verification
$webhookKey = $request->header('webhook-key');
if ($webhookKey !== config('mesilat.webhook_key')) {
return response('Unauthorized', 401);
}
// Data retrieval
$data = $request->all();
$uuid = $data['uuid'];
// Idempotency check
if (Order::where('transaction_uuid', $uuid)->exists()) {
return response('OK', 200); // Already processed
}
// Add to queue for processing
ProcessWebhookJob::dispatch($data);
// Quick response
return response('OK', 200);
});
// Node.js (Express)
app.post('/webhooks/mesilat', async (req, res) => {
// Webhook key verification
const webhookKey = req.headers['webhook-key'];
if (webhookKey !== process.env.MESILAT_WEBHOOK_KEY) {
return res.status(401).send('Unauthorized');
}
const data = req.body;
const uuid = data.uuid;
// Idempotency check
const exists = await Order.findOne({ transactionUuid: uuid });
if (exists) {
return res.status(200).send('OK'); // Already processed
}
// Add to queue
await queue.add('processWebhook', data);
// Quick response
res.status(200).send('OK');
});
# Python (Flask)
@app.route('/webhooks/mesilat', methods=['POST'])
def webhook():
# Webhook key verification
webhook_key = request.headers.get('webhook-key')
if webhook_key != os.getenv('MESILAT_WEBHOOK_KEY'):
return Response('Unauthorized', status=401)
data = request.json
uuid = data['uuid']
# Idempotency check
if Order.query.filter_by(transaction_uuid=uuid).first():
return Response('OK', status=200) # Already processed
# Add to queue
process_webhook.delay(data)
# Quick response
return Response('OK', status=200)
Debugging
Logging
It's recommended to log all incoming webhooks for debugging:
// PHP
Log::info('Webhook received', [
'uuid' => $data['uuid'],
'product_id' => $data['product_id'],
'amount' => $data['amount'],
'trigger_type' => $data['trigger_type'],
'data' => $data
]);
Testing
Use "Send test event" function in webhook settings to check integration before going to production.
Delivery Verification
In platform personal cabinet you can see webhook sending history and status of each delivery attempt.