# 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's `apiKey` in the `x-api-key` header.
* `BASIC_AUTHENTICATION`: Trendyol uses the `username` and `password` you 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:

```json
{
  "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:

```bash
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.

```javascript
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:

```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](/event-gateway) checks `x-api-key` on 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 `2xx` as 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](/docs/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 `getShipmentPackages` and 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 `status` to upper case before comparing.

**How Hookdeck Can Help:** Hookdeck [transformations](/docs/transformations) can normalise the status before the request reaches your service, and [filters](/docs/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](/webhooks/guides/implement-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](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## 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](https://hookdeck.com) 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](https://dashboard.hookdeck.com/signup) for free and handle Trendyol webhooks reliably in minutes.