Guide to Trendyol Webhooks: Features and Best Practices
Trendyol webhooks let sellers on the Turkish e-commerce marketplace hear about order packages as they change, instead of polling the getShipmentPackages service. When a package is created, picked, invoiced, shipped, delivered, cancelled or returned, Trendyol posts the full order package to your endpoint, so your warehouse, invoicing and customer service systems can react straight away.
This guide covers how Trendyol webhooks are created, the statuses and the payload they send, how to authenticate them with API key authentication, how retries and deactivation work, and the practices that keep a Trendyol integration in sync.
What are Trendyol webhooks?
A Trendyol webhook is an order package subscription that you create through Trendyol's integration API. It has a URL, an authentication type and a list of package statuses. When a package reaches one of those statuses, Trendyol sends a POST with a JSON body to your URL.
The body is always the full order data in the same model that getShipmentPackages returns, whatever the status, so one handler can process every webhook the same way.
Trendyol doesn't sign webhook requests. Every webhook must use one of two authentication types:
API_KEY: Trendyol sends the webhook'sapiKeyin thex-api-keyheader.BASIC_AUTHENTICATION: Trendyol uses theusernameandpasswordyou set on the webhook.
Either way, it's a static credential that your endpoint compares with the value you stored.
Trendyol webhook features
| Feature | Details |
|---|---|
| Configuration | API only: POST /integration/webhook/sellers/{sellerId}/webhooks |
| Method | POST with a JSON body |
| Payload | The full order package, in the getShipmentPackages model |
| Status field | status on each package in content |
| Authentication | API_KEY (x-api-key header) or BASIC_AUTHENTICATION. No signature |
| Subscriptions | Any of 13 statuses. Empty means all, including future ones |
| Limit | 15 webhooks per seller, deactivated ones included |
| URL rule | Must not contain "Trendyol", "Dolap" or "Localhost" |
| Retries | Every 5 minutes until the request succeeds |
| Deactivation | After two warning emails, if the webhook keeps failing |
| Source IPs | Not published |
Statuses and payload
You can subscribe to these package statuses: CREATED, PICKING, INVOICED, SHIPPED, CANCELLED, DELIVERED, UNDELIVERED, RETURNED, UNSUPPLIED, AWAITING, UNPACKED, AT_COLLECTION_POINT and VERIFIED.
Each request is a page of packages, as in getShipmentPackages. Here is the shape of Trendyol's example, heavily trimmed:
{
"totalElements": 1,
"totalPages": 1,
"page": 0,
"size": 1,
"content": [
{
"shipmentPackageId": 3330111111,
"orderNumber": "10654411111",
"status": "Delivered",
"shipmentPackageStatus": "Delivered",
"orderDate": 1762253333685,
"lastModifiedDate": 1762865408581,
"cargoProviderName": "Trendyol Express",
"cargoTrackingNumber": 7280027504111111,
"packageTotalPrice": 498.90,
"currencyCode": "TRY",
"packageHistories": [
{ "createdDate": 1762242537624, "status": "Created" }
],
"lines": [
{ "quantity": 1 }
]
}
]
}
The real package is much larger. It includes the shipment and invoice addresses, customer details, every order line with prices, discounts and cancellation details, cargo tracking links, invoice fields and delivery estimates. Times are milliseconds since the epoch. createdBy tells you how the package came about: order-creation, cancel (after a partial cancellation), split or transfer (redirected to another seller).
Watch the casing. The subscription list uses upper case (DELIVERED), but the example payload writes "status": "Delivered", so compare statuses case-insensitively. The multi-word AT_COLLECTION_POINT has no example, so log the first one you receive.
Setting up Trendyol webhooks
Generate a long random API key for the webhook, for example with openssl rand -hex 32. Then create the webhook, authenticating the call with your seller API key and secret from Hesap Bilgilerim > Entegrasyon Bilgileri (account information, integration information) in the seller panel:
curl -X POST "https://apigw.trendyol.com/integration/webhook/sellers/$SELLER_ID/webhooks" \
-u "$TRENDYOL_API_KEY:$TRENDYOL_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/orders",
"authenticationType": "API_KEY",
"apiKey": "<your random value>",
"subscribedStatuses": ["CREATED", "SHIPPED", "DELIVERED", "CANCELLED", "RETURNED"]
}'
The response is the webhook's id, which you need to update or delete it. Use stageapigw.trendyol.com to test against Trendyol's stage environment. Leave subscribedStatuses empty to receive every status, including any Trendyol adds later.
Remember the URL rule: the address can't contain "Trendyol", "Dolap" or "Localhost", so a path like /webhooks/trendyol isn't allowed. Use a neutral path.
With Hookdeck. Choose API key authentication (authenticationType API_KEY) when you create the Trendyol webhook. Create a Hookdeck source with the Trendyol type, paste the same apiKey into API Key, and use the source URL as the webhook url. Hookdeck compares it with the x-api-key header on each request. Webhooks that use BASIC_AUTHENTICATION aren't checked by this source. The source accepts POST only, which is what Trendyol sends.
Securing Trendyol webhooks
Read x-api-key, compare it with your stored key in constant time, and only then parse the body.
const crypto = require('crypto');
const express = require('express');
const API_KEY = process.env.TRENDYOL_WEBHOOK_API_KEY;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/orders', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get('x-api-key');
if (!API_KEY || typeof received !== 'string' || !safeEqual(received, API_KEY)) {
return res.sendStatus(401);
}
let page;
try {
page = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
for (const pkg of page.content ?? []) {
const status = String(pkg.status).toUpperCase(); // "Delivered" -> "DELIVERED"
// Upsert by shipmentPackageId, ignoring anything older than lastModifiedDate you already hold.
}
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
API_KEY = os.environ.get("TRENDYOL_WEBHOOK_API_KEY", "")
def verify_trendyol_request(headers) -> bool:
if not API_KEY:
return False # fail closed when unconfigured
received = headers.get("x-api-key") # Starlette and Flask headers are case-insensitive
if not received:
return False
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), API_KEY.encode("utf-8"))
A static key authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Use HTTPS, a long random key that is different from your seller API credentials, and update the webhook and your receiver together if it leaks.
Make Trendyol webhooks production-ready. Hookdeck Event Gateway checks
x-api-keyon every order package, answers Trendyol straight away, and delivers each package to your systems with retries you control.
Trendyol webhook limitations and pain points
Failing webhooks get switched off
The Problem: After a long outage or a bug that returns errors, order updates stop arriving.
Why It Happens: Trendyol re-sends failed requests every 5 minutes. If the webhook keeps failing, it sends one email saying how long it will keep trying, then a second saying the period is over and the webhook has been deactivated. You have to reactivate it yourself after fixing the problem.
Workarounds:
- Make sure the people who receive seller emails know what these two emails mean.
- Answer with a
2xxas soon as the key checks out, and process afterwards.
How Hookdeck Can Help: Hookdeck accepts each package as soon as it arrives, so a problem in your own service doesn't count as a failure on Trendyol's side. Hookdeck then delivers to your service with retries on your schedule, and you can replay anything that failed once it's fixed.
Webhooks aren't the whole story
The Problem: Your system is missing an order status change that Trendyol shows.
Why It Happens: Trendyol says that sending data by webhook isn't always possible, and recommends syncing periodically with getShipmentPackages as well.
Workarounds:
- Run a scheduled sync with
getShipmentPackagesand reconcile it with what the webhooks delivered.
How Hookdeck Can Help: Hookdeck keeps a log of every package that arrived, which makes it easy to see which updates came by webhook when you reconcile.
Status casing
The Problem: A handler matching "DELIVERED" never fires.
Why It Happens: The documented statuses are upper case, but the example payload shows "Delivered".
Workarounds:
- Normalise
statusto upper case before comparing.
How Hookdeck Can Help: Hookdeck transformations can normalise the status before the request reaches your service, and filters can route packages by status.
A limited number of webhooks and a URL rule
The Problem: You can't create another webhook, or Trendyol rejects your URL.
Why It Happens: A seller can have at most 15 webhooks, and deactivated ones count. URLs containing "Trendyol", "Dolap" or "Localhost" aren't allowed.
Workarounds:
- Delete webhooks you no longer use, and use one webhook with several statuses rather than one per status.
- Choose neutral paths, and use a public tunnel rather than a localhost URL for development.
How Hookdeck Can Help: One Hookdeck source can take all your Trendyol traffic and fan it out to several services, so you don't need extra Trendyol webhooks for extra consumers. Hookdeck's Trendyol source compares x-api-key with the key you paste, answers Trendyol with a 200 either way, and records a request without the right key as failed verification, so it never reaches your destination.
Best practices
Treat each request as a page
Loop over content. Don't assume one package per request.
Upsert by package, newest wins
Key on shipmentPackageId, and only overwrite a stored package when lastModifiedDate is newer. Retries and the 5-minute resend can deliver older states after newer ones. See our guide to webhook idempotency.
Subscribe to what you handle
Name the statuses you process in subscribedStatuses, or leave it empty and acknowledge the ones you don't use with a 2xx, so they never count as failures.
Keep credentials separate
The webhook apiKey is what Trendyol sends to you. Your seller API key and secret are what you send to Trendyol. Never use one for the other.
Acknowledge fast
Verify, store the page, return 200, and update stock, invoices and shipping afterwards. See why to process webhooks asynchronously.
Conclusion
Trendyol sends the full order package whenever it reaches a status you subscribed to, authenticated by a static apiKey in x-api-key rather than a signature. Create the webhook with API_KEY authentication, compare the header in constant time, loop over content, normalise the status casing, upsert by shipmentPackageId and lastModifiedDate, and keep a periodic getShipmentPackages sync, because a webhook that keeps failing is eventually deactivated.
Hookdeck Event Gateway sits in front of your receiver, checks the key, answers Trendyol at once, and keeps every package until your systems have processed it.
Get started with Hookdeck for free and handle Trendyol webhooks reliably in minutes.