# Treet Webhooks

Treet (treet.co) runs branded resale and trade-in marketplaces. Its webhooks are set up by the Treet team on request (topic, trigger and URL) and currently cover one topic, **Shipping Label**. Each request carries an `X-Treet-Signature` header: the base64 HMAC-SHA256 of the raw request body, keyed with a secret Treet issues to you.

## When to Use This Skill

* How do I receive Treet webhooks?
* How do I verify the `X-Treet-Signature` header?
* How do I handle `label/created` and `label/updated` events?
* Why does my Treet webhook endpoint return 401?
* Why are my Treet webhooks unsigned?

## Verification (core)

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

function verifyTreetSignature(rawBody, signature, secret = process.env.TREET_WEBHOOK_SECRET) {
  if (!secret) return false; // never accept when unconfigured
  // Whole header value, exact form: 43 base64 characters plus "=" padding
  if (typeof signature !== 'string' || !/^[A-Za-z0-9+/]{43}=$/.test(signature)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); // 44 bytes each
}

```

* **Header:** `X-Treet-Signature`, bare standard base64 (padded), no prefix. Compare the whole value.
* **Signed content:** the raw body bytes only. No timestamp, id or other header is signed, so there is no replay window to check.
* **Key:** the secret as UTF-8 bytes, used as-is (not hex- or base64-decoded). Env var: `TREET_WEBHOOK_SECRET`.
* **Raw body:** verify the exact bytes (`express.raw({ type: '*/*' })`, `await request.arrayBuffer()`, `await request.body()`). Never `JSON.parse`/`JSON.stringify` before verifying.
* **Exact comparison:** the base64 is case-sensitive. Validate the format, then compare the strings in constant time. Decoding alone isn't enough: a value whose last character carries stray bits decodes to the same bytes but Hookdeck rejects it, so you must too.
* **Python:** `hmac.compare_digest` on bytes, never `str`.
* Missing or empty header, malformed value or mismatch: `401`. Signed but malformed body: `400`.

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

## Signing Is Opt-In

Treet issues the secret key on request (support@treet.co). Without it, no signature header is sent. Hookdeck shows this note on its Treet source:

> Signing is opt-in: ask Treet support for a secret key and paste it here. Without one, deliveries are unsigned and fail verification.

## HTTP Methods

Treet's docs don't name the HTTP method. Hookdeck's Treet source pre-selects POST without managing the method (you can change it), so any other method the sender uses must also be allowed on the source. The examples accept GET, POST, PUT, PATCH and DELETE, verify the signature first for every method, and treat a missing body as empty bytes: a signed GET with no body is verified over zero bytes and acknowledged with `200`.

## Event Types

Dispatch on the body's `trigger` field. `topic` is the coarser topic (`label`) and is the same for both.

| `trigger` | Fires when |
| --- | --- |
| `label/created` | A shipping label is created |
| `label/updated` | A shipping label changes (e.g. `status` becomes `SHIPPED`) |

Log and acknowledge any other trigger. After verifying, treat every field as untrusted and check types before reading into nested values. Treet documents nothing about retries, timeouts or the expected response, so return `2xx` once handled and make handlers idempotent.

## Environment Variables

```bash
TREET_WEBHOOK_SECRET=your_treet_secret_key   # issued by Treet support

```

## Local Development

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

```

No account required. Use the printed URL when asking Treet to register your webhook.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/treet-webhooks/references/overview.md) - What Treet webhooks are, events and payload
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/treet-webhooks/references/setup.md) - Request webhooks and a secret key from Treet
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/treet-webhooks/references/verification.md) - Signature details, gotchas, debugging
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/treet-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests