Skip to content

Voucher integration

This guide explains how to set up an on-demand voucher pool so that RE-ZIP generates vouchers dynamically by calling your server, instead of drawing from a pre-uploaded pool of codes. This is the recommended integration path for engineering teams that want full control over voucher generation: your system decides the code, the redirect link, the discount, and the conditions at the moment a customer earns a reward.

  • A RE-ZIP shop account with a valid access token (see Getting started)
  • A publicly reachable HTTPS endpoint on your side that can receive webhook requests from RE-ZIP (see Webhooks for general webhook setup, signature verification, and retry behaviour)

Throughout this guide $SHOP_ID refers to your shop’s ULID and $TOKEN is your Bearer token.

With a static pool (the default), you upload a fixed set of voucher codes in advance and RE-ZIP draws from that pool when a customer scans packaging.

With an on-demand pool, there are no pre-uploaded codes. Instead, the pool carries a webhook_url pointing at your server. When a customer scans RE-ZIP packaging at a drop point, RE-ZIP calls your webhook and your server responds with a freshly generated voucher: code, redirect link, discount details, and all.

Customer scans packaging
RE-ZIP finds on-demand pool for shop + country
RE-ZIP POSTs to your webhook_url
Your server generates a voucher and responds
Customer receives the voucher with a redirect link to your webshop

If your webhook is unreachable or returns an error, RE-ZIP falls back to any available static pool for that shop.

The visibility field controls how vouchers from your pool are distributed:

Visibility Meaning
private Only your own customers receive these vouchers. When a customer scans packaging belonging to your shop, they get a voucher from this pool.
public Available as a fallback for customers of other shops that don’t have their own vouchers.
private_public Both: your customers get these vouchers, and they are also available as a fallback for the wider public.

For most integrations you will want private or private_public.

Create a pool with pool_type set to on_demand and provide your webhook_url:

Terminal window
$ curl -s -X POST \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "Webshop voucher generation",
"countries": ["DK", "DE"],
"visibility": "private",
"pool_type": "on_demand",
"webhook_url": "https://your-server.example.com/rezip/voucher"
}' \
https://api.re-zip.com/shops/$SHOP_ID/vouchers/pools | pp.json
{
"id": "01JAYSCS07SVDQANFS61TF0XJN",
"name": "Webshop voucher generation",
"countries": ["DK", "DE"],
"visibility": "private",
"pool_type": "on_demand",
"webhook_url": "https://your-server.example.com/rezip/voucher",
"created_at": "2026-04-22T10:00:00.000Z",
"updated_at": "2026-04-22T10:00:00.000Z"
}

The webhook_url is required when pool_type is on_demand. Omitting it returns a 400 validation error.

On-demand pools do not use batches or pre-uploaded codes. Attempting to create a batch on an on-demand pool will return 400.

When a customer earns a voucher, RE-ZIP sends a POST request to your webhook_url:

POST /rezip/voucher HTTP/1.1
Host: your-server.example.com
Content-Type: application/json
Webhook-Version: 1
Signature-Input: sig1=("@method" "@target-uri" "content-type");keyid="key-a1b2c3d4";alg="ecdsa-p384-sha384";created=1700000000
Signature: sig1=:BASE64_ENCODED_SIGNATURE:
{
"voucher_pool_id": "01JAYSCS07SVDQANFS61TF0XJN"
}

Like all RE-ZIP webhooks, the request is signed using HTTP Message Signatures (RFC 9421). See the Webhooks guide for how to verify the Signature and Signature-Input headers.

The voucher_pool_id identifies which pool the request is for, useful if you have multiple on-demand pools configured (e.g. one per country or campaign).

Your server must respond with a 2xx status and a JSON body containing the voucher details:

{
"code": "SHOP-A1B2C3",
"link": "https://shop.example.com/redeem?code=SHOP-A1B2C3",
"type": "percentage",
"value": 15,
"unit": "%",
"conditions": "Min. purchase 200 DKK",
"expires_at": "2027-01-01T00:00:00.000Z"
}
Field Type Description
code string The voucher code the customer will see
link string A redirect URL; the customer is sent here to redeem the voucher on your webshop
type string Discount type: amount, percentage, or gift
value integer Discount value, in minor units for amount (e.g. 5000 for 50 DKK), whole number for percentage (e.g. 15), quantity for gift
unit string ISO 4217 currency code for amount (e.g. "DKK", "EUR"), "%" for percentage, or item description for gift
conditions string Free-text conditions shown to the customer
expires_at string ISO 8601 expiry timestamp

See the Vouchers guide for a detailed breakdown of how each field is displayed to the customer.

The link field is what makes this powerful for deep integrations: when the customer receives the voucher, they are taken directly to your webshop with the discount ready to apply, rather than having to copy-paste a code manually.

If your webhook returns a non-2xx status, times out, or returns an invalid response body, RE-ZIP logs the failure and falls back to any available static voucher pool for that shop. The customer still receives a reward, just not one generated by your webhook.

This means you do not need 100% uptime on your webhook endpoint. However, you should monitor your error rate and aim for reliable responses since on-demand vouchers are typically more valuable to your customers than the static fallback.

A Node.js webhook handler with signature verification (see Webhooks for details on verifying signatures):

import { randomBytes } from "node:crypto"
import { verifySignature } from "./rezip-webhook-verify.js" // your verification helper
app.post("/rezip/voucher", async (req, res) => {
// Verify the request signature (see the Webhooks guide)
const isValid = await verifySignature(req)
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" })
}
const { voucher_pool_id } = req.body
const code = `SHOP-${randomBytes(4).toString("hex").toUpperCase()}`
const link = `https://shop.example.com/redeem?code=${code}`
// Create the discount code in your e-commerce system here
// ...
res.json({
code,
link,
type: "amount",
value: 5000, // minor units: 5000 = 50.00 DKK
unit: "DKK",
conditions: "Min. purchase 200 DKK",
expires_at: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(),
})
})

In a real integration you would generate the code in your e-commerce platform (Shopify, WooCommerce, custom system, etc.) and return its details. The key point is that the code and redirect link are created at the moment the customer earns the reward, not ahead of time.

List all pools for your shop:

Terminal window
$ curl -s \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
https://api.re-zip.com/shops/$SHOP_ID/vouchers/pools/ | pp.json

Get a specific pool:

Terminal window
$ curl -s \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
https://api.re-zip.com/shops/$SHOP_ID/vouchers/pools/$POOL_ID | pp.json

Delete a pool:

Terminal window
$ curl -s -X DELETE \
-H 'Accept-Version: 2.0' \
-H 'Authorization: Bearer $TOKEN' \
https://api.re-zip.com/shops/$SHOP_ID/vouchers/pools/$POOL_ID
Action Method Endpoint
Create pool POST /shops/{id}/vouchers/pools
List pools GET /shops/{id}/vouchers/pools/
Get pool GET /shops/{id}/vouchers/pools/{pool_id}
Delete pool DELETE /shops/{id}/vouchers/pools/{pool_id}
Direction RE-ZIP → your server
Method POST
URL Your webhook_url
Request body { "voucher_pool_id": "<ULID>" }
Request header Webhook-Version: 1
Expected response 2xx with { code, link, type, value, unit, conditions, expires_at }
On failure RE-ZIP falls back to static pools