# Guide to SHOPLINE Webhooks: Features and Best Practices

SHOPLINE webhooks notify your app about store activity: an order is created, a product updates, a collection changes. If you're building on the SHOPLINE Open Platform, webhooks are how you react to store events without polling. If you've integrated Shopify, this will feel familiar, SHOPLINE's webhook model is a close clone.

This guide covers how SHOPLINE webhooks work, the topics you'll subscribe to, how to verify the `X-Shopline-Hmac-Sha256` header, the retry behavior, and the best practices for production.

## What are SHOPLINE webhooks?

SHOPLINE Open Platform (developer.shopline.com) webhooks are HTTP POSTs delivered to a URL you subscribe per topic. They're signed with an HMAC-SHA256 of the raw body, keyed with your app secret, in the `X-Shopline-Hmac-Sha256` header, Shopify-style. Companion headers identify the topic and shop.

## SHOPLINE webhook features

| Feature | Details |
| --- | --- |
| Configuration | Admin REST API: `POST https://{handle}.myshopline.com/admin/openapi/{version}/webhooks.json` (body must include `api_version`) |
| Signature header | `X-Shopline-Hmac-Sha256` |
| Signature scheme | HMAC-SHA256 of the raw body, keyed with the app secret |
| Encoding | lowercase hex (verified against real deliveries) |
| Other headers | `X-Shopline-Topic`, `X-Shopline-Shop-Domain`, `X-Shopline-Shop-Id`, `X-Shopline-Webhook-Id` (stable across resends) |
| Topics | Shopify-style slash format (`orders/create`) |
| Acknowledgement | 5-second timeout |
| Retries | Up to 19 over 48h, then the subscription is auto-removed |
| SDK | None published |

## Common topics

SHOPLINE topics use the Shopify-style slash format:

| Topic | Fires when |
| --- | --- |
| `orders/create` | An order is created |
| `orders/updated` | An order updates |
| `products/create` | A product is created |
| `products/update` | A product updates |
| `collect/delete` | A collect is deleted |

Subscribe to the topics you process, and consult SHOPLINE's webhook reference for the full list.

## Setting up SHOPLINE webhooks

Subscribe via the Admin REST API: `POST https://{handle}.myshopline.com/admin/openapi/{version}/webhooks.json` with `{ topic, address, api_version }`. Your app secret (Developer Center > App credentials) is the HMAC key.

`api_version` is required in the body. Omit it and the request fails with a `400`, even though the URL already contains a version segment. Include `api_version` explicitly in the JSON payload.

## Securing SHOPLINE webhooks

Each delivery carries an `X-Shopline-Hmac-Sha256` header: a lowercase-hex HMAC-SHA256 of the raw request body, keyed with your app secret. Verify against the raw, un-parsed body and compare in constant time. (Despite the Shopify-style header name, SHOPLINE uses hex, not base64, confirmed against real deliveries.)

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

const APP_SECRET = process.env.SHOPLINE_APP_SECRET;

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

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-shopline-hmac-sha256"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge within 5s
  processQueue.add({
    topic: req.headers["x-shopline-topic"],
    body: JSON.parse(req.body),
  });
});

```

The same check in Python:

```python
import hashlib
import hmac
import os

APP_SECRET = os.environ["SHOPLINE_APP_SECRET"].encode()

def verify(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(APP_SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

```

## SHOPLINE webhook limitations and pain points

### `api_version` is required on creation

The Problem: `POST /webhooks.json` returns a `400` if the request body doesn't include `api_version`, even though the URL path already carries a version segment. The failure isn't obviously about the missing field, so it's easy to lose time on it.

Why It Happens: SHOPLINE validates `api_version` from the body, not the URL, on webhook creation.

Workarounds:

* Always include `api_version` in the JSON body (alongside `topic` and `address`).

How Hookdeck Can Help: Once created, point the webhook `address` at Hookdeck for verification, queuing, and retries, so the one-time creation quirk is behind you and delivery is reliable from then on.

### Two developer platforms

The Problem: SHOPLINE has a newer Open Platform (developer.shopline.com) and an older one (open-api.docs.shoplineapp.com). Following the wrong docs leads to wrong headers or endpoints.

Why It Happens: SHOPLINE runs two developer platforms.

Workarounds:

* Use the newer Open Platform (the one this guide and the source align with).

How Hookdeck Can Help: Hookdeck's filters and normalization insulate your app from platform differences.

### The 5-second timeout and auto-removal

The Problem: You must respond within 5 seconds, and after up to 19 retries over 48 hours the subscription is auto-removed, so a sustained outage loses the subscription entirely.

Why It Happens: SHOPLINE caps the ack time and prunes failing subscriptions.

Workarounds:

* Acknowledge fast, process off a queue, and monitor/recreate a removed subscription; dedupe on `X-Shopline-Webhook-Id` (stable across resends).

How Hookdeck Can Help: Hookdeck acknowledges within the window, deduplicates on the webhook id, and retries to your downstream on its own schedule, so the subscription stays alive. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Raw-body handling

The Problem: The signature is over the raw body, so a JSON parser upstream breaks verification.

Why It Happens: The signed content is the exact bytes.

Workarounds:

* Capture the raw body before parsing.

How Hookdeck Can Help: Hookdeck verifies against the bytes as received.

## Best practices

### Include api_version when creating webhooks

Put `api_version` in the `POST /webhooks.json` body, or creation fails with a `400`.

### Verify the lowercase-hex signature over the raw body

Compute a lowercase-hex HMAC-SHA256 over the raw body with the app secret and compare in constant time.

### Acknowledge within 5 seconds, process asynchronously

Return 200 quickly and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe on the webhook id

Use `X-Shopline-Webhook-Id` (stable across resends) as an idempotency key.

### Use the newer Open Platform

Follow developer.shopline.com for headers, topics, and the subscribe endpoint.

## Conclusion

SHOPLINE webhooks are a Shopify-style clone, with two things the docs get wrong or gloss over: the `X-Shopline-Hmac-Sha256` signature is lowercase hex (not base64), and webhook creation requires `api_version` in the body or it 400s. Verify the hex HMAC over the raw body, use the slash-format topics, acknowledge within 5 seconds, and dedupe on the webhook id to survive the 48-hour retry window and auto-removal.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique store events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle SHOPLINE webhooks reliably in minutes.