# Trendyol Webhooks

Trendyol (the Turkish marketplace) sends a webhook to your seller endpoint when an order package
changes status. Webhooks are **not signed**. When you create the webhook with
`authenticationType` `API_KEY`, Trendyol sends the webhook's `apiKey` as plain text in the
`x-api-key` header on every request, and your endpoint authenticates the request by comparing the
whole header value with the key you stored.

> Choose API key authentication (authenticationType API_KEY) when you create the Trendyol webhook.

Trendyol also offers `BASIC_AUTHENTICATION`, which Hookdeck's Trendyol source does not cover; this
skill covers only `API_KEY`.

## When to Use This Skill

* How do I receive Trendyol order package webhooks?
* How do I verify the `x-api-key` header on a Trendyol webhook?
* Does Trendyol sign webhook payloads? (No: use the shared API key)
* How do I handle `CREATED`, `SHIPPED`, `DELIVERED`, `CANCELLED` or `RETURNED` packages?
* Why does my Trendyol webhook endpoint return 401, or why does Trendyol keep re-sending requests?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyTrendyolRequest(headers, secret = process.env.TRENDYOL_WEBHOOK_SECRET) {
  if (!secret) return false;                  // never accept when unconfigured
  const received = headers['x-api-key'];      // the apiKey, sent as plain text
  return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}

```

* Header: `x-api-key: <apiKey>`. Compare the **whole** value with `TRENDYOL_WEBHOOK_SECRET`; there is no
  prefix, HMAC or timestamp. Look the header up case-insensitively (Node lowercases it already).
* Return `401` for a missing or wrong key, `400` for a body that isn't a JSON object with a
  `content` array, `200` once handled.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.
* In Python use `hmac.compare_digest` on bytes (`a.encode()`, `b.encode()`); passing `str` raises
  `TypeError` on non-ASCII input.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Serve the endpoint over HTTPS and rotate the
  `apiKey` (update the webhook) if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/trendyol-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/trendyol-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/trendyol-webhooks/examples/fastapi/).

## Event Types

Trendyol sends the **full order package** whatever the status. The body is a page of packages,
`{ "content": [ ... ] }`; loop over `content[]` and dispatch on each package's `status`. The
documentation lists the statuses in upper case but the example payload writes them in Title case
(`"status": "Delivered"`), so compare case-insensitively.

| Status | Package is |
| --- | --- |
| `CREATED` | Created from a new order |
| `PICKING` | Being picked by the seller |
| `INVOICED` | Invoiced |
| `SHIPPED` | Handed to the carrier |
| `DELIVERED` | Delivered to the customer |
| `UNDELIVERED` | Not delivered |
| `CANCELLED` | Cancelled |
| `RETURNED` | Returned |
| `UNSUPPLIED` | Marked as not supplied by the seller |
| `AWAITING` | Awaiting |
| `UNPACKED` | Unpacked |
| `AT_COLLECTION_POINT` | Waiting at a collection point |
| `VERIFIED` | Verified |

Requests are `POST` with a JSON body. If your endpoint fails, Trendyol re-sends every 5 minutes
until it succeeds and eventually deactivates the webhook, so handlers must be idempotent.

## Environment Variables

```bash
TRENDYOL_WEBHOOK_SECRET=your-webhook-api-key   # the apiKey set when creating the webhook

```

## Local Development

Trendyol can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 trendyol --path /webhooks/trendyol

```

Use the printed URL as the webhook URL (Trendyol rejects URLs containing "Trendyol", "Dolap" or
"Localhost").

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/trendyol-webhooks/references/overview.md) - What Trendyol webhooks are, statuses and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/trendyol-webhooks/references/setup.md) - Create the webhook with an API key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/trendyol-webhooks/references/verification.md) - Key check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/trendyol-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests