Webhooks
TurnStay sends real-time notifications to your server when events occur — payment completions, transaction updates, refunds, and more.
TurnStay has two webhook surfaces. Subscribe to the events for the product you integrate:
| Surface | Examples | Docs |
|---|---|---|
| Merchant of Record | merchant_of_record.payment_intent.succeeded, refunds, MOR payouts | Payment Events |
| Treasury | treasury.payment.completed, treasury.withdraw.completed, deposits, transfers | Treasury Events |
Receiving MOR events on an endpoint does not mean Treasury events (such as claim confirmations) are delivered. Each event type must be subscribed explicitly.
Configure webhook endpoints in the TurnStay Dashboard under Settings > Webhooks.
Webhook payload format
Section titled “Webhook payload format”All webhooks follow this structure:
{ "type": "merchant_of_record.payment_intent.succeeded", "data": { "object": { "id": "45678", "object": "payment_intent", "status": "PROCESSED", "billing_amount": 100000, "billing_currency": "ZAR", "processing_amount": 5200, "processing_currency": "EUR", "merchant_reference": "BOOKING-2026-001", "company_id": 10, "account_id": 123 } }}Handling webhooks
Section titled “Handling webhooks”Your webhook endpoint must:
- Accept
POSTrequests with a JSON body. - Return HTTP
200to acknowledge receipt. - Be publicly accessible (not behind a VPN).
- Handle duplicate events gracefully (idempotency).
@app.route("/webhooks/turnstay", methods=["POST"])def turnstay_webhook(): event = request.get_json() if event["type"] == "merchant_of_record.payment_intent.succeeded": intent = event["data"]["object"] mark_booking_as_paid( reference=intent["merchant_reference"], amount=intent["billing_amount"], ) return jsonify({"ok": True}), 200Retry behaviour
Section titled “Retry behaviour”If your endpoint does not return HTTP 200, TurnStay retries delivery with exponential backoff. Ensure your handler is idempotent — the same event may be delivered more than once.
Error responses
Section titled “Error responses”When an API request fails, TurnStay returns a JSON error response:
{ "detail": "billing_amount is required"}| HTTP status | Meaning |
|---|---|
400 | Bad request — check the detail field for the cause. |
401 | Unauthorized — invalid or missing API key. |
404 | Resource not found. |
405 | Method not allowed — check the HTTP method. |
422 | Validation error — invalid field values. |
500 | Server error — retry the request or contact support. |
Callbacks (legacy)
Section titled “Callbacks (legacy)”If you set callback_url when creating a Payment Intent, TurnStay also POSTs to {your_callback_url}/callback/payment/intent. See Payment Events for the callback payload format.
Webhooks are recommended over callbacks for new integrations.