> ## Documentation Index
> Fetch the complete documentation index at: https://www.zippendo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom channel API

> Create a custom order channel, copy the ingest URL and signing secret, set every Operations toggle, and push a signed order.

A **custom channel** is for a webshop, ERP, or WMS Zippendo does not have a one-click integration for. This page is every control on create and settings, plus how to send an order. The catalog walkthrough is [Connect order channels](/docs/tutorials/connect-order-channels). Payload reference: [Custom channels](/docs/order-channels/custom-channels).

Open **Order Channels** → **Available integrations** (`?tab=available-integrations`). The **Custom channel** card reads **Connect your own webshop or ERP system**. Features: **Signed webhook order ingestion**, **Full REST API with official SDKs**, **Automatic shipment creation and dispatch**. The button is **Create channel**.

<Frame caption="Custom channel card on Available integrations">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/01-available-integrations-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=90d7a28241f23e4faf3dcc1f11ce5ad8" alt="Order Channels Available integrations tab with the Custom channel card and Create channel button" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/01-available-integrations-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/01-available-integrations-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=718d64189ff1b46578f4873d9f578ece" alt="Order Channels Available integrations tab with the Custom channel card and Create channel button" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/01-available-integrations-dark.png" />
</Frame>

## Create the channel

Click **Create channel**. Modal id `create-custom-channel`. Title **Create custom channel**. Subtitle: **A channel for your own webshop or ERP integration.**

<Frame caption="Create custom channel modal with Channel name">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/02-create-modal-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=6dd6d240bffdb0c2150240eb48133b17" alt="Create custom channel modal with Channel name filled as My webshop, Create channel, and Cancel" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/02-create-modal-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/02-create-modal-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=7b56d1f08458ecd82903a945d91abe9c" alt="Create custom channel modal with Channel name filled as My webshop, Create channel, and Cancel" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/02-create-modal-dark.png" />
</Frame>

| Control            | Required | Copy in the UI                                                                                                                                                                        |
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Channel name**   | Yes      | Placeholder `e.g. My webshop`. **Create channel** stays disabled until the name is non-empty.                                                                                         |
| **Brand**          | No       | The whole field (label **Brand** plus picker) is shown only if the organization has brands. Helper: **Orders from this channel belong to this brand.** Hidden in orgs with no brands. |
| Hint               | —        | **After creating the channel you will get an ingest URL and a signing secret, so your system can push orders to Zippendo.**                                                           |
| **Create channel** | —        | Becomes **Creating…**. Toast **Order channel created successfully** / **Failed to create order channel**.                                                                             |
| **Cancel**         | —        | Closes the modal.                                                                                                                                                                     |

On success Zippendo opens the new channel’s settings.

## Order ingestion

Header title is the channel name. Description: **Your own integration: push orders to the ingest URL, or use the API and SDKs.** **View documentation** opens the [custom channels](/docs/order-channels/custom-channels) reference. Loading: **Loading channel…**

<Frame caption="Order ingestion with Ingest URL and Generate secret">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/03-order-ingestion-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=0b11dd4599552b0aaa38cd826e6af1f3" alt="Order ingestion card with HMAC description, Ingest URL, Copy, and Generate secret or Rotate secret" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/03-order-ingestion-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/03-order-ingestion-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=a7931f95dbd507e28635f2dd6f4bafe2" alt="Order ingestion card with HMAC description, Ingest URL, Copy, and Generate secret or Rotate secret" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/03-order-ingestion-dark.png" />
</Frame>

Card title **Order ingestion**. Description: **Your system pushes signed order events to the ingest URL below. Every request must carry an HMAC-SHA256 signature of the raw body, computed with the signing secret.**

| Control             | Copy in the UI                                                                                                                                                                          |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Ingest URL**      | Read-only. Production looks like `https://api.zippendo.com/webhooks/order-channels/{channelId}`. **Copy** → **Copied to clipboard.** / **Could not copy to clipboard.**                 |
| **Generate secret** | First time. Becomes **Generating…**. Toast **Signing secret generated.** / **Could not generate the signing secret.**                                                                   |
| **Rotate secret**   | After a secret exists. Confirm: **Rotate the signing secret? Your integration will be rejected until it signs with the new secret.**                                                    |
| **Revoke**          | Confirm: **Revoke the signing secret? All order pushes will be rejected until a new secret is generated.** Toast **Signing secret revoked.** / **Could not revoke the signing secret.** |
| Active line         | **Active signing secret created \{date}**                                                                                                                                               |

<Warning>
  The **Ingest URL** in screenshots may show `http://localhost:8000/…` because these docs were captured against a local environment. Copy the URL from **your** channel settings.
</Warning>

After **Generate secret** or **Rotate secret**, the plaintext is shown once:

<Frame caption="Copy the signing secret immediately — it is only shown once">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/04-signing-secret-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=3552a9be29be0a54e5a03f907c1c51de" alt="Order ingestion with one-time signing secret, Copy, Revoke, and Rotate secret" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/04-signing-secret-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/04-signing-secret-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=1ad4adda43e174ae0e73bb2864f2db0e" alt="Order ingestion with one-time signing secret, Copy, Revoke, and Rotate secret" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/04-signing-secret-dark.png" />
</Frame>

Warning: **Copy the secret now — it is only shown once. Rotating it later invalidates the current one immediately.** The secret starts with `zwhs_`. There is no “enable webhooks” toggle — minting a signing secret is what turns ingest on.

## Send an order

`POST` the **Ingest URL** with two headers:

| Header                 | Value                                                                               |
| ---------------------- | ----------------------------------------------------------------------------------- |
| `X-Zippendo-Topic`     | `order.created`, `order.updated`, `order.cancelled`, or `ping`                      |
| `X-Zippendo-Signature` | HMAC-SHA256 of the **raw request body**, hex-encoded, keyed with the signing secret |

`order.created` and `order.updated` both upsert on `externalId`. `order.cancelled` body is `{ "externalId": "…" }`. `ping` returns `200 { "received": true }` without touching data.

```javascript theme={null}
import crypto from "node:crypto"

const body = JSON.stringify(order)
const signature = crypto.createHmac("sha256", process.env.ZIPPENDO_WEBHOOK_SECRET)
  .update(body)
  .digest("hex")

await fetch(webhookUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Zippendo-Topic": "order.created",
    "X-Zippendo-Signature": signature
  },
  body
})
```

Required payload fields: `externalId`, `orderNumber`, `orderLines` (at least one line with `name` and `quantity`). Optional: `customerName`, `customerEmail`, `shippingAddress`, `subtotalAmount`, `totalAmount`, `currency`, `notes`, `shippingMethodTitle`, `serviceCode`. Full field table: [Custom channels](/docs/order-channels/custom-channels).

| Status | Meaning                                                                                            |
| ------ | -------------------------------------------------------------------------------------------------- |
| `200`  | Accepted and processed                                                                             |
| `400`  | Missing/unknown topic, or the payload failed validation                                            |
| `401`  | Signature failed (wrong secret, missing header, or no secret minted) — `WEBHOOK_SIGNATURE_INVALID` |
| `404`  | Unknown channel id                                                                                 |
| `429`  | Ingest rate limit — retry after `Retry-After`                                                      |
| `5xx`  | Processing failed — retry; upsert makes redelivery safe                                            |

When **Channel enabled** is off, pushes are rejected until you turn it back on.

REST alternative: `POST /orgs/{orgId}/orders` with `orderChannelId` and an [API token](/docs/how-to/create-an-api-token). Duplicate `externalId` on the same channel returns `409 ORDER_EXISTS`.

Tracking back to your system uses org [Webhooks](/docs/how-to/webhooks) (`shipment.dispatched`, `tracking.updated`), not a per-channel callback. There is no shipping-method mapping UI on this screen.

A successful push lands under **Orders**. Status can be **Error** when Zippendo imported the order but could not create a ready shipment (no matching shipping rule or incomplete address). The order is still on the channel.

<Frame caption="Orders list after a signed ingest">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/08-ingested-order-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=b47fbbd0f7c2d64d7106a0cadb5e62e8" alt="Orders list showing an ingested custom-channel order" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/08-ingested-order-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/08-ingested-order-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=1c4e22cb2f58acfdcfd26baa66735b9c" alt="Orders list showing an ingested custom-channel order" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/08-ingested-order-dark.png" />
</Frame>

## Operations

<Frame caption="Operations — Channel enabled, Auto-dispatch, Brand, Default sender address">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/05-operations-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=8cef4068c49a57a52f424b36b7e0e862" alt="Operations card with Channel enabled on, Auto-dispatch shipments, Default sender address, Last order received, and Save changes" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/05-operations-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/05-operations-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=35dd239cf3d380bc4580c293e3987268" alt="Operations card with Channel enabled on, Auto-dispatch shipments, Default sender address, Last order received, and Save changes" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/05-operations-dark.png" />
</Frame>

| Field                       | Saves            | Copy in the UI                                                                                                                               |
| --------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| **Channel enabled**         | Immediately      | **When disabled, all order pushes to this channel are rejected.** This is the enable/disable control.                                        |
| **Auto-dispatch shipments** | **Save changes** | **Book the carrier automatically when an incoming order matches a shipping rule.** Off by default.                                           |
| **Brand**                   | Immediately      | Label **Brand**. Helper: **Orders from this channel belong to this brand.** The picker itself only appears when the organization has brands. |
| **Default sender address**  | **Save changes** | **Used for shipments when no shipping rule matches the order.** Placeholder **Select address**. Empty: **No addresses yet**.                 |
| **Last order received**     | Display          | **When this channel last received an order.** Timestamp, or **Never**.                                                                       |

**Save changes** / **Saving…** applies auto-dispatch and the default address. Toast **Order channel updated successfully** / **Failed to update order channel**.

## Enable, disable, and disconnected

Turn **Channel enabled** off. The **Connected stores** card shows **Disabled** (not **Disconnected**). Custom channels stay on **Connected stores** — they are never leftover **Disconnected channels** rows.

<Frame caption="Disabled custom channel on Connected stores">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/06-disabled-card-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=461cbe431b3e234e969df61e0728b33b" alt="Connected stores custom channel card with Disabled badge, Custom integration subtitle, and No orders received yet or Last order received" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/06-disabled-card-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/06-disabled-card-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=a02598d98990e5c7f9caf69769a61a28" alt="Connected stores custom channel card with Disabled badge, Custom integration subtitle, and No orders received yet or Last order received" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/06-disabled-card-dark.png" />
</Frame>

An **Active** custom card shows **Custom integration**, **Connected** date, and **Last order received \{date}** or **No orders received yet**.

Leftover platform stores (no live integration) appear under **Disconnected channels** with **No active integration** and **Remove**. That is not a custom channel. See [Order channel settings](/docs/how-to/order-channel-settings).

## Delete

**Danger zone** → **Delete channel**. Description: **Remove this custom channel. Your integration will no longer be able to push orders.**

<Frame caption="Danger zone Delete channel">
  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/07-danger-zone-light.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=d7ee24e3c4546d9c2836ddb282c51503" alt="Danger zone with Delete channel and Delete button" className="block dark:hidden" width="2880" height="1800" data-path="images/how-to/custom-channel-api/07-danger-zone-light.png" />

  <img src="https://mintcdn.com/zippendo/-jr41VsJfvHKJJ2U/images/how-to/custom-channel-api/07-danger-zone-dark.png?fit=max&auto=format&n=-jr41VsJfvHKJJ2U&q=85&s=82c69dcf654a69a6139761182b82ab36" alt="Danger zone with Delete channel and Delete button" className="hidden dark:block" width="2880" height="1800" data-path="images/how-to/custom-channel-api/07-danger-zone-dark.png" />
</Frame>

Confirm: **Delete this custom channel? Its ingest URL stops accepting orders immediately.** Button **Delete** / **Deleting…**. Toast **Order channel deleted successfully** / **Failed to delete order channel**.

## Related

<CardGroup cols={2}>
  <Card title="Connect order channels" icon="store" href="/docs/tutorials/connect-order-channels">
    Create the channel from the catalog, then open Orders.
  </Card>

  <Card title="Custom channels reference" icon="webhook" href="/docs/order-channels/custom-channels">
    Full ingest payload, cancel, ping, and REST examples.
  </Card>

  <Card title="Order channel settings" icon="settings-2" href="/docs/how-to/order-channel-settings">
    Enable, disable, and leftover disconnected channels.
  </Card>

  <Card title="Create an API token" icon="key" href="/docs/how-to/create-an-api-token">
    Token for POST /orgs/\{orgId}/orders instead of signed ingest.
  </Card>
</CardGroup>
