Skip to content

Webshop integration

This guide covers the end-to-end flow for offering RE-ZIP packaging in your own webshop: how to find the packaging SKUs available to your shop, how to record a packaging order when a customer chooses reusable packaging, and how to react to order state changes.

It is written for teams building their own storefront — a headless frontend (Shopify Hydrogen, Next.js, Nuxt), a custom checkout, or a platform we don’t ship a plugin for. If you run a standard Shopify Liquid theme you don’t need any of this: our Shopify app implements the whole flow for you. Email helpdesk@re-zip.com and we’ll get you set up with it.

  • A RE-ZIP shop account with an access token or PAT (see Getting started)
  • Your shop’s ULID, referred to below as $SHOP_ID
  • A publicly reachable HTTPS endpoint, if you want order webhooks (see Webhooks)

Throughout this guide $TOKEN is your token. Orders accept Bearer and Pat authorization; for a server-side integration a scoped PAT is the better choice. Develop against https://api.sandbox.re-zip.com and switch the host to https://api.re-zip.com when you go live. Every request needs an Accept-Version header; the current production version is 2.0.

Customer adds RE-ZIP packaging to their basket
│
▼
Your checkout completes the order
│
▼
Your server POSTs the packaging lines to /shops/$SHOP_ID/orders
│
▼
RE-ZIP validates the SKUs against your assortment and creates the order
│
▼
RE-ZIP fulfils the packaging; order.updated fires as the state changes
│
▼
Customer returns the packaging at a drop point and earns a voucher

Your storefront owns the customer-facing half: showing the option, pricing it, and carrying the choice through checkout. RE-ZIP owns the packaging half: the assortment, fulfilment, the return network, and the reward.

Every shop has an assortment — the set of packaging products it may order. An order is rejected if it references a SKU outside that set, so read the assortment first rather than hardcoding SKUs.

Terminal window
$ curl -s \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
https://api.re-zip.com/shops/$SHOP_ID/products | pp.json
[
{
"id": "01JAYSCS07SVDQANFS61TF0XJN",
"sku": "REZIP-BAG-M",
"name": "RE-ZIP Bag Medium",
"price": 1200,
"active": true,
"currency": "DKK",
"dimension": { "width": 250, "height": 80, "depth": 350 },
"weight": 95,
"volume": 7000,
"surface_area": 1750,
"primary_material": "rPET",
"bundle_size": 50,
"group": {
"name": "Bags",
"description": "Reusable mailer bags"
},
"created_at": "2026-04-22T10:00:00.000Z",
"updated_at": "2026-04-22T10:00:00.000Z",
"deleted_at": null
}
]

Notes on the fields that matter for a storefront:

FieldUse
skuThe identifier you send when creating an order. The only field the order endpoint reads.
name, group.nameSafe to show to customers
priceThe price RE-ZIP charges you, in minor units. Not a retail price — see below.
currencyThe currency price is denominated in, resolved from your shop’s ordering settings
activeFilter out anything not true
dimension, weight, volumeFor a size picker, or to match packaging to basket contents. dimension is {width, height, depth} in millimetres, any of which may be null.

The Shops scope reference lists every field the endpoint returns.

price is your cost, not what you charge your customer. Whether you pass it on, absorb it, or charge something else entirely is your decision, and it lives in your storefront and your own product catalogue — RE-ZIP never sees your retail price.

Cache the assortment rather than fetching it per pageview. It changes when your agreement changes, which is rare.

Step 2: Offer packaging in your storefront

Section titled “Step 2: Offer packaging in your storefront”

This step is entirely yours, and it varies by platform, so the guide only states the contract: by the time an order is placed, your backend needs to know which SKU the customer chose and how many units.

How you carry that through checkout is up to you. Two common shapes:

  • A product in your own catalogue. Create a product (or variant per SKU) in your commerce platform, mapped to a RE-ZIP SKU. The line item flows through checkout, your fulfilment, and your accounting like any other product. This is what the Shopify app does, and it’s the shape to copy if your logistics provider needs to see the packaging as a sellable line.
  • A cart attribute or order metadata field. Lighter weight, but invisible to your fulfilment pipeline unless you propagate it yourself.

If you are on a headless storefront, note that you cannot reuse the Shopify app’s widget: it is a theme app extension and depends on Liquid and the Ajax Cart API. Build the opt-in into your own components and mutate the cart with your platform’s own API.

When the customer’s order is placed, POST the packaging lines to RE-ZIP. Do this server-side — it needs your token, which must never reach the browser.

Terminal window
$ curl -s -X POST \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"currency": "DKK",
"lines": [
{ "sku": "REZIP-BAG-M", "quantity": 2 },
{ "sku": "REZIP-BOX-S", "quantity": 1 }
]
}' \
https://api.re-zip.com/shops/$SHOP_ID/orders | pp.json
{
"id": "01JAYSDB1037Y6X8WSJ1EZQ03X",
"number": 1042,
"state": 0,
"type": "normal",
"aggregate_price_at_creation": 3600,
"lines": [
{
"id": "01JAYSDB1037Y6X8WSJ1EZQ040",
"sku": "REZIP-BAG-M",
"name": "RE-ZIP Bag Medium",
"dimensions": "W: 250.0mm, H: 80.0mm, D: 350.0mm",
"price": 1200,
"quantity": 2,
"fulfilled": 0,
"created_at": "2026-04-22T10:00:00.000Z",
"updated_at": "2026-04-22T10:00:00.000Z"
},
{
"id": "01JAYSDB1037Y6X8WSJ1EZQ041",
"sku": "REZIP-BOX-S",
"name": "RE-ZIP Box Small",
"dimensions": "W: 200.0mm, H: 100.0mm, D: 300.0mm",
"price": 1200,
"quantity": 1,
"fulfilled": 0,
"created_at": "2026-04-22T10:00:00.000Z",
"updated_at": "2026-04-22T10:00:00.000Z"
}
],
"created_at": "2026-04-22T10:00:00.000Z",
"updated_at": "2026-04-22T10:00:00.000Z"
}
FieldTypeDescription
currencystringISO 4217 code the order is denominated in (e.g. "DKK", "EUR")
linesarrayOne entry per packaging SKU
lines[].skustringA SKU from your assortment (Step 1)
lines[].quantitynumberHow many units

name, dimensions, and price on each response line are resolved from your assortment at creation time — you do not send them, and sending them has no effect. aggregate_price_at_creation is the order total in minor units, frozen at creation so a later assortment price change doesn’t rewrite history.

A newly created order has state: 0, meaning open. It leaves that state as RE-ZIP fulfils it; subscribe to order.updated (Step 4) rather than polling.

StatusCause
400A SKU is not in your assortment. The message names it: SKU not in assortment: <sku>.
404$SHOP_ID is not a shop, or is not linked to a partner
401Missing or invalid token

A 400 here is a configuration problem, not a transient one — retrying will not fix it. Log it, alert on it, and fall back to completing the customer’s order without RE-ZIP packaging rather than failing their checkout.

The endpoint has no idempotency key. Two identical POSTs create two orders. Since a retry after a network timeout cannot tell whether the first attempt landed, protect against duplicates on your side:

  • Record the returned order id against your own order before you consider the step done, and skip the call if one is already recorded
  • Drive the call from a queue or job with at-most-once semantics, not from the request that completes checkout
  • If you do end up unsure, GET /shops/$SHOP_ID/orders and reconcile by created_at before retrying

Rather than polling for fulfilment, subscribe to order events. Two events are available: order.created and order.updated.

Terminal window
$ curl -s -X POST \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"event": "order.updated",
"url": "https://your-server.example.com/rezip/order-updated"
}' \
https://api.re-zip.com/shops/$SHOP_ID/webhooks

An unknown event name returns 400. Deliveries are signed with HTTP Message Signatures (RFC 9421) — see Webhooks for verification and retry behaviour. Verify the signature before acting on the payload.

order.created fires for orders you created yourself too, so if you already record the order id from the Step 3 response, order.updated is usually the only subscription you need.

When a customer drops packaging at a drop point, RE-ZIP issues them a voucher for your shop. There are two ways to supply those vouchers: upload a static pool of codes up front, or register an on-demand pool, where RE-ZIP calls your server to mint one at the moment it’s earned.

If you can run a webhook endpoint, use an on-demand pool. You are already building the integration in this guide, so you have the one prerequisite, and it is the better option on every axis that matters:

  • You cannot run out. A static pool is inventory: finite codes that deplete as customers redeem them. An on-demand pool has nothing to deplete, because the code is generated per request.
  • Running dry is silent. When a static pool is exhausted, nothing errors. The reward falls down an internal chain to your configured default voucher, and then to a generic ticket. Your customer gets something less than you intended, you are not told, and the first signal is usually a support email.
  • It’s set-up-and-forget. The generation logic lives in your codebase, under version control, reviewed like the rest of your code — rather than as a batch of codes somebody has to remember to top up. You configure the pool once and move on.
  • The code can be real at the moment it’s earned. Mint it in your own commerce platform inside the webhook handler and return a link that drops the customer into a basket with the discount already applied, instead of handing them a code to copy.

Uptime is not a concern: if your endpoint is unreachable or errors, RE-ZIP falls back to any static pool you have. So a small static pool is a reasonable safety net underneath an on-demand one — not a replacement for it.

That flow has its own guide: Voucher integration. Read Vouchers first if you want to know how each field is rendered to the customer.

Each scope publishes an OpenAPI 3.1 document, so you don’t have to hand-write request and response types. See OpenAPI Docs for the download links; the endpoints in this guide are all in the Shops scope.

ActionMethodEndpoint
List assortmentGET/shops/{id}/products
Create packaging orderPOST/shops/{id}/orders
List ordersGET/shops/{id}/orders
Get orderGET/shops/{id}/orders/{order_id}
Create webhookPOST/shops/{id}/webhooks
List webhooksGET/shops/{id}/webhooks

Integrating and stuck on something this guide doesn’t cover? Email helpdesk@re-zip.com, or ask us to set up a shared Slack channel.