# API & webhooks

Premium brands can receive **signed JSON webhooks** when orders are paid or
fulfilled on Drape. Configure the endpoint in the brand portal under
[Integrations](https://brand.drape.to/integrations).

There is no public third-party REST catalog API yet. Day-to-day catalog, orders,
and analytics stay in the brand portal / app; webhooks cover outbound order
events for your own systems (ERP, fulfillment, Slack bots, etc.).

## Prerequisites

| Requirement | Detail |
|---|---|
| Plan | **Premium** (`custom_integrations` entitlement) |
| Portal | [brand.drape.to/integrations](https://brand.drape.to/integrations) |
| HTTPS endpoint | Must accept `POST` with JSON body |
| Response | Return **2xx** within ~10 seconds |

## Configure in the portal

1. Open **Integrations**.
2. Under **Order webhooks**, set your endpoint URL.
3. Select events (`order.paid`, `order.fulfilled`).
4. Save — copy the **signing secret** when it is shown (create / rotate only).
5. Keep the webhook **active**.

Rotate the secret anytime with **Rotate secret**, then update your verifier.

## Events

| Event | When it fires |
|---|---|
| `order.paid` | Shopper payment succeeds for an order that includes your store’s lines |
| `order.fulfilled` | You mark the order shipped / fulfilled in Drape |

Default subscription when events are omitted: both of the above.

## Delivery

Drape `POST`s to your URL with:

| Header | Value |
|---|---|
| `Content-Type` | `application/json` |
| `X-Drape-Event` | Event name, e.g. `order.paid` |
| `X-Drape-Signature` | Hex HMAC-SHA256 of the **raw body** using your signing secret |

Timeout: **10 seconds**. Non-2xx or network errors are logged as failed
deliveries; retries are not guaranteed yet — treat handlers as idempotent.

### Envelope shape

```json
{
  "id": "9f3c2a1b-…",
  "type": "order.paid",
  "created_at": "2026-09-10T13:00:00.000000Z",
  "store_id": "c373fbc1-…",
  "data": {
    "order": {
      "id": "…",
      "store_id": "…",
      "status": "paid",
      "total": 189.0
    }
  }
}
```

`data.order` is the serialized order row for your store (UUIDs as strings,
datetimes as ISO-8601). Fields may grow over time — ignore unknown keys.

## Verify the signature

Compute HMAC-SHA256 over the **exact request body bytes** with your secret and
compare to `X-Drape-Signature` (hex digest).

### Node.js

```js
import crypto from "node:crypto";

function verifyDrapeSignature(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(signatureHeader || "", "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: use express.raw({ type: "application/json" }) for this route
app.post("/webhooks/drape", (req, res) => {
  const ok = verifyDrapeSignature(
    req.body,
    req.get("X-Drape-Signature"),
    process.env.DRAPE_WEBHOOK_SECRET,
  );
  if (!ok) return res.status(401).send("invalid signature");
  const event = JSON.parse(req.body.toString("utf8"));
  // handle event.type …
  res.status(200).json({ received: true });
});
```

### Python

```python
import hmac
import hashlib

def verify_drape_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")
```

## Brand portal API (authenticated)

Webhook settings are managed with the brand JWT (same session as the portal):

| Method | Path | Notes |
|---|---|---|
| `GET` | `/api/brand/integrations/webhook` | Current config (secret not returned) |
| `PUT` | `/api/brand/integrations/webhook` | Body: `{ "url", "events?", "is_active?" }` — returns `secret` on create |
| `POST` | `/api/brand/integrations/webhook/rotate-secret` | Returns new `secret` |

Base URL: `https://api.drape.to`. Requires Premium; otherwise `403`.

## Related

- Website button / shop bridge: [Website widget & deep links](./deep-links.md)
- Brand plans: [For brands](./for-brands.md)
- Portal: [Integrations](https://brand.drape.to/integrations)
