Payment Notification API¶
Use this endpoint when your payment system needs to tell Traveln that a booking payment has completed, failed, been cancelled, or been refunded.
This is a partner-to-Traveln server callback. Traveln uses it to:
- validate the notification against the original invoice
- record the incoming payment event for audit and idempotency
- update the Traveln invoice status
Endpoint¶
POST https://integrations.app.traveln.ai/api/v1/payments/notify/
Authentication¶
Send your tenant partner API key in the X-API-KEY header:
X-API-KEY: <partner_api_key>
Content-Type: application/json
This endpoint does not use Authorization: Api-Key <sso_hmac_secret>.
When to call it¶
Call this endpoint after your backend has received and validated the final payment result from your gateway or PSP.
Typical flow:
- Traveln creates an invoice for the booking flow.
- Traveln calls your configured payment API endpoint and hands your system the payment context.
- Your system creates the payment session and sends the traveler to your hosted payment flow or gateway page.
- Once your backend has a definitive payment outcome, it calls Traveln
POST /api/v1/payments/notify/. - Traveln records the event and updates the invoice status.
Use your backend for this handoff. Do not call this endpoint from browser code.
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
invoice_id | string (UUID) | Yes | Traveln invoice identifier for the booking payment |
transaction_id | string | Yes | Your payment transaction reference or gateway transaction ID |
status | string | Yes | Payment outcome. Use paid, failed, cancelled, or refunded |
amount | string or number | Yes | Must exactly match the Traveln invoice amount |
currency | string | Yes | Must exactly match the Traveln invoice currency |
gateway | string | Yes | Must match the tenant payment gateway. Supported values in the current implementation: telr, moyasar |
Additional JSON fields are allowed. Traveln stores the full payload for audit purposes, but only the fields above are used for validation and invoice status updates.
Status mapping¶
Traveln currently maps incoming payment statuses as follows:
Incoming status | Traveln invoice status |
|---|---|
paid | paid |
failed | failed |
cancelled | failed |
refunded | refunded |
If you send any other status string, the event is still stored, but the invoice status is not transitioned by this endpoint.
Idempotency¶
Traveln deduplicates notifications using this tuple:
transaction_idstatusgateway
If the same combination is sent again, Traveln responds with:
{
"status": "duplicate"
}
This lets your integration safely retry the same notification without creating duplicate audit events or applying the same invoice transition twice.
Validation rules¶
Traveln accepts the notification only if all of the following are true:
- the
X-API-KEYbelongs to an active tenant - the
invoice_idexists - the invoice belongs to the authenticated tenant
- the
gatewaymatches the tenant's configured payment gateway - the
amountmatches the original invoice amount - the
currencymatches the original invoice currency
On accepted notifications, Traveln also stores transaction_id as the invoice gateway_reference.
Example request¶
curl "https://integrations.app.traveln.ai/api/v1/payments/notify/" \
-X POST \
-H "X-API-KEY: <partner_api_key>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"invoice_id": "2f9db4bb-8cf6-4c84-a27d-5db8ca7cf0af",
"transaction_id": "pay_01JQ5V6D4W8VXZ9Q8K53Q0N1B7",
"status": "paid",
"amount": "1499.00",
"currency": "AED",
"gateway": "moyasar",
"gateway_payload": {
"provider_status": "paid"
}
}'
const res = await fetch("https://integrations.app.traveln.ai/api/v1/payments/notify/", {
method: "POST",
headers: {
"X-API-KEY": "<partner_api_key>",
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
invoice_id: "2f9db4bb-8cf6-4c84-a27d-5db8ca7cf0af",
transaction_id: "pay_01JQ5V6D4W8VXZ9Q8K53Q0N1B7",
status: "paid",
amount: "1499.00",
currency: "AED",
gateway: "moyasar",
gateway_payload: {
provider_status: "paid",
},
}),
})
const payload = await res.json()
console.log(payload)
import requests
res = requests.post(
"https://integrations.app.traveln.ai/api/v1/payments/notify/",
headers={
"X-API-KEY": "<partner_api_key>",
"Accept": "application/json",
"Content-Type": "application/json",
},
json={
"invoice_id": "2f9db4bb-8cf6-4c84-a27d-5db8ca7cf0af",
"transaction_id": "pay_01JQ5V6D4W8VXZ9Q8K53Q0N1B7",
"status": "paid",
"amount": "1499.00",
"currency": "AED",
"gateway": "moyasar",
"gateway_payload": {
"provider_status": "paid",
},
},
timeout=30,
)
payload = res.json()
print(payload)
Example responses¶
Success¶
{
"status": "success"
}
Duplicate notification¶
{
"status": "duplicate"
}
Error responses¶
| HTTP status | Example body | Meaning |
|---|---|---|
401 | {"error": "Missing API key"} | X-API-KEY header was not sent |
401 | {"error": "Unauthorized"} | API key is invalid or the tenant is inactive |
400 | {"error": "Missing invoice_id"} | A required field is missing |
404 | {"error": "Invoice not found"} | invoice_id does not match a Traveln invoice |
403 | {"error": "Forbidden"} | The invoice belongs to a different tenant |
400 | {"error": "Gateway mismatch"} | The provided gateway does not match the tenant configuration |
400 | {"error": "Amount mismatch"} | The amount does not match the original invoice |
400 | {"error": "Currency mismatch"} | The currency does not match the original invoice |
Integration notes¶
- Your integration must retain the Traveln
invoice_idfor the payment being reported. This callback cannot work without it. - If your current payment-initiation handoff does not already persist
invoice_idin your payment record, add that before enabling this callback flow. - Send a stable
transaction_idfor retries. That is what makes duplicate retries safe. - Send only final or meaningfully state-changing outcomes. Re-sending the same status is safe, but unnecessary.
- When
statusispaid, Traveln marks the invoice as paid and setspaid_at. - When
statusisrefunded, Traveln marks the invoice as refunded.