Confirm the payment
Know for sure that you've been paid. Receive a webhook when the status of an order changes, and check the status from your server before you deliver.
Every integration ends the same way: before you confirm an order to your customer or deliver, your server retrieves the order from myTU and checks its status. A webhook tells you when to do that.
Every payment is an order with one or more transactions. The order stands for what your customer buys. A transaction is a payment attempt for that order, with a specific payment method. Both have a status, and you base your business logic on one of them.
Which status to check
| Integration | Check | Why |
|---|---|---|
| Hosted payment page and payment links | Order status | Your customer can try several payment methods, so an order can have several transactions. The order status sums them up. |
| Your own checkout | Transaction status | The order has exactly one transaction. The order stays processing for up to 60 minutes, while the transaction status is final as soon as the payment is done. |
When to check
Retrieve the order with GET /v1/orders/{order_id}/ to get the latest status at any moment. To make sure you never miss a status change, combine these three mechanisms:
- When your customer returns. Retrieve the order as soon as your customer lands on your
return_url, and show the result. - On a webhook. Retrieve the order when you receive a webhook. Webhooks are fast, but can be delayed or lost because of network problems, so don't rely on them alone.
- Periodically. Retrieve every order that isn't final yet (
neworprocessing) at a regular interval. Pick an interval that suits your business: if you reserve concert tickets for 15 minutes, check within those 15 minutes.
Don't trust the redirect
Your customer can close the browser before they're redirected, and anyone can open your return_url. Always retrieve the order from your server before you confirm the payment or deliver.
Retrieve the order
curl https://api.gw.mytu.co/v1/orders/4c6afd74-a840-4aab-b411-1e6e0636d341/ \
--user "$API_KEY:"The response contains the order and all of its transactions. This example shows a completed card payment (shortened):
{
"id": "4c6afd74-a840-4aab-b411-1e6e0636d341",
"amount": 100,
"currency": "EUR",
"description": "Example order #1",
"merchant_order_id": "EXAMPLE001",
"return_url": "https://www.example.com/order/EXAMPLE001",
"created": "2026-09-20T08:51:10.078503+00:00",
"completed": "2026-09-20T08:52:02.357623+00:00",
"status": "completed",
"transactions": [
{
"id": "6a0bdf56-53c3-4ce3-8189-4a73d775ac66",
"amount": 100,
"currency": "EUR",
"payment_method": "credit-card",
"payment_method_brand": "visa",
"payment_method_details": {
"truncated_pan": "414141******4141",
"vault_token": "e3ed069e-c931-40ae-8035-e022e8a4e5e7"
},
"events": [
{
"event": "new",
"id": "3cb2259b-a3f9-4908-bb98-eb51c773fc70",
"occurred": "2026-09-20T08:51:10.091203+00:00",
"source": "set_status"
}
],
"status": "accepted"
}
]
}See Get an order in the API reference for all fields.
Act on the order status:
| Status | What to do |
|---|---|
completed | The payment succeeded. Confirm the order and deliver. |
new, processing | Not final yet. Show a "payment pending" message and wait for the next webhook. |
cancelled, expired, error | No payment. Let your customer try again. |
see-transactions | The transactions have different statuses. Don't take automatic action: check the individual transactions. |
See Order and transaction statuses for what every status means.
Webhooks
A webhook is an HTTP POST request that myTU sends to your server every time the status of an order changes. Webhooks keep your system up to date without constant polling, and they matter most when your customer doesn't come back to your website after paying, for example because they closed the browser.
Set the webhook URL
You can set the webhook URL in two places:
| Where | Scope |
|---|---|
| Project settings in the merchant portal at https://merchant.gw.mytu.co | Every order of the project. |
webhook_url field when you create an order | Only that order. Overrides the URL of the project. |
curl https://api.gw.mytu.co/v1/orders/ \
--user "$API_KEY:" \
--header "Content-Type: application/json" \
--data '{
"currency": "EUR",
"amount": 995,
"merchant_order_id": "EXAMPLE001",
"description": "Example order",
"return_url": "https://www.example.com/order/EXAMPLE001",
"webhook_url": "https://www.example.com/webhook"
}'What you receive
myTU sends a POST request with a JSON body to your webhook URL:
POST /webhook HTTP/1.1
Host: www.example.com
Content-Type: application/json
{
"event": "status_changed",
"order_id": "4c6afd74-a840-4aab-b411-1e6e0636d341",
"project_id": "b5f39273-44e7-4385-8e08-44612ef3e117"
}event is always status_changed: the status of the order with the order_id changed. See Order status changed in the API reference.
The webhook deliberately doesn't contain the status. Retrieve the order and act on the status in that response, so you only act on data that comes from myTU directly.
Respond to the webhook
Respond with HTTP status 200 OK. The response body can be empty: myTU ignores it.
Delivery counts as failed when:
- your server responds with a
4xxor5xxstatus code; - connecting to your webhook URL times out;
- reading the response from your server times out;
- your server refuses the connection.
The timeout for connecting and reading is 4 seconds for the first attempt and 10 seconds for every retry. Respond quickly: if you need to do heavy work, store the notification and process it in the background.
Retries
After a failed delivery, myTU retries the webhook every 2 minutes. After the 10th failed attempt, myTU stops trying.
Example handler
This handler retrieves the order and stores its status. updateOrderInYourShop and update_order_in_your_shop stand for your own code:
import express from 'express';
const app = express();
app.use(express.json());
const authorization = `Basic ${Buffer.from(`${process.env.API_KEY}:`).toString('base64')}`;
app.post('/webhook', async (req, res) => {
try {
const response = await fetch(`https://api.gw.mytu.co/v1/orders/${req.body.order_id}/`, {
headers: { Authorization: authorization },
});
if (!response.ok) throw new Error(`Status request failed: ${response.status}`);
const order = await response.json();
await updateOrderInYourShop(order.merchant_order_id, order.status);
res.sendStatus(200);
} catch (error) {
console.error(error);
res.sendStatus(500); // myTU retries the webhook
}
});
app.listen(3000);Best practices
- Make your handler idempotent. Because of retries, you can receive the same notification more than once. Processing it twice must not, for example, ship an order twice.
- Always fetch the order. Never derive the status from the webhook itself.
- Have a backup. A webhook can be delayed or lost. Regularly retrieve orders that aren't final yet, as described in When to check.
- Allow the traffic. If a firewall, WAF or bot protection runs in front of your server, make sure it doesn't block webhook requests.
- Use HTTPS for your webhook URL.