On this page
Getting a key
Keys are issued by the ShopZen team, one partner at a time.
- 1
Apply by email
Write to [email protected] with your company name, website and logo, the app name your users will see, your exact HTTPS redirect URLs, the permissions you need (and why), and a technical contact.
- 2
ShopZen reviews it
A ShopZen administrator checks your company and trims the permissions to what your app actually uses.
- 3
Sandbox key
You get a sandbox client ID and secret. Build and test the whole flow — test stores, test orders, no real money.
- 4
Production key
Show us the go-live checklist working (a short screen recording is fine) and we issue your production key.
Keep the client secret on your server — never in a mobile app, browser code or a public repository. If it leaks, email us and we rotate it straight away.
Connecting a store
“Connect ShopZen” uses OAuth 2.0 with PKCE — the same flow as “Sign in with Google”. Each connection is one of your users plus one of their stores.
- 1Your server makes a random state and code verifier, then sends the user to ShopZen's Connect page.
- 2They sign in — or create a ShopZen store right there — pick a store, see what you asked for and press Allow.
- 3ShopZen sends them back to your redirect URL with a one-time
code(valid 10 minutes). Checkstatefirst. - 4Your server swaps the code for tokens. Save the store ID, both tokens and the granted scopes.
https://shopzen.bd/connect
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fshopzen%2Fcallback
&scope=store.read%20products.write%20orders.read
&state=RANDOM_STATE
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256curl https://api.shopzen.bd/oauth/token \
-d grant_type=authorization_code \
-d code=AUTH_CODE \
-d redirect_uri=https://app.example.com/shopzen/callback \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d code_verifier=CODE_VERIFIER{
"access_token": "szp_at_7f3c…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "szp_rt_91ab…",
"scope": "store.read products.write orders.read",
"store": { "id": "cmtss9wvd0008fuhdzv49z27m", "name": "Rahim Fashion", "url": "https://rahim.shopzen.bd" }
}Access tokens last 1 hour. Refresh them with grant_type=refresh_token; each refresh returns a new refresh token and the old one stops working. Using an old one again ends the connection, as it looks stolen. Refresh tokens expire after 90 days unused.
Users can disconnect from your app (call POST /oauth/revoke) or from ShopZen, under Settings → Connected apps. Either way the tokens stop working at once.
Making requests
Sandbox and production are fully separate: separate keys, stores, tokens and webhooks.
Connect page
Sandbox · https://sandbox.shopzen.bd/connect
Live · https://shopzen.bd/connect
Token endpoint
Sandbox · https://api.sandbox.shopzen.bd/oauth/token
Live · https://api.shopzen.bd/oauth/token
API base URL
Sandbox · https://api.sandbox.shopzen.bd/partner/v1
Live · https://api.shopzen.bd/partner/v1
curl https://api.shopzen.bd/partner/v1/orders?status=PENDING \
-H "Authorization: Bearer szp_at_7f3c…"- Format
- JSON in and out. Results are under data; lists add next_cursor.
- Money
- Whole numbers in paisa:
149000is ৳1,490. Never send decimals. - Times
- ISO 8601 in UTC, like 2026-09-25T08:30:00.000Z.
- Your IDs
- Products, variants, orders and customers take an
external_id— your own ID — so you never have to store ours. - Pages
- Up to 200 items per page (50 by default). Pass next_cursor back as ?cursor= until it is null.
- Retries
- Send an
Idempotency-Keyon every POST. A retry with the same key within 24 hours returns the first result instead of doing the work twice. - Rate limit
- 120 requests a minute per connected store, with short bursts. Over it, you get 429 and a Retry-After header.
Permissions
Ask only for what you use — users approve a short list more readily. The user may grant fewer than you asked for.
store.readThe store's name, address, currency and status.
products.readProducts, variants, categories and stock.
products.writeCreate, update and archive products; set stock and prices.
orders.readOrders and their items.
orders.writeChange order status and add tracking.
customers.readCustomers — name, phone, email, addresses — and their orders.
analytics.readThe sales summary.
products.write includes products.read, and orders.write includes orders.read. Calling an endpoint without its permission returns 403 insufficient_scope.
Endpoints
All paths are under /partner/v1, and every call only ever sees the store the token was issued for.
Store
GET/store
The connected store and its status
Products
PUT/products/by-external-id/{external_id}
Create or update one product by your ID
POST/products/bulk
Up to 500 products in one background job
GET/products
List, with status, category and search filters
GET/products/{id}
One product
PATCH/products/{id}
Change some fields, like the status
DELETE/products/{id}
Archive — hidden from the store, kept on past orders
GET/categories
All categories
POST/inventory
Set stock for up to 1,000 variants
Orders
GET/orders
List, filtered by status and dates
GET/orders/{id}
One order with items, delivery and payment
POST/orders/{id}/status
Move it along: paid, fulfilled, canceled
POST/orders/{id}/tracking
Add the courier and tracking number
PATCH/orders/{id}
Link it to the order in your system
Customers and sales
GET/customers
List and search customers
GET/customers/{id}
One customer with addresses and totals
GET/customers/{id}/orders
Their orders, newest first
GET/analytics/summary
Sales, orders and top products for a date range
Webhooks
POST/webhooks
Register an endpoint (up to 5 per store)
GET/webhooks
Your endpoints
DELETE/webhooks/{id}
Remove one
curl -X PUT https://api.shopzen.bd/partner/v1/products/by-external-id/8812 \
-H "Authorization: Bearer szp_at_7f3c…" \
-H "Content-Type: application/json" \
-d '{
"title": "Navy cotton panjabi",
"category": "Men / Panjabi",
"images": ["https://cdn.example.com/8812-front.jpg"],
"variants": [
{ "external_id": "8812-M", "sku": "PNJ-NVY-M", "options": { "Size": "M" }, "price": 185000, "stock": 12 },
{ "external_id": "8812-L", "sku": "PNJ-NVY-L", "options": { "Size": "L" }, "price": 185000, "stock": 8 }
]
}'ShopZen copies product photos to its own storage, so they keep working if your links change later.
Webhooks
ShopZen posts to your HTTPS endpoint the moment something happens. Answer with any 2xx within 10 seconds and do the work afterwards.
order.createdA shopper places an order
order.updatedStatus, tracking or notes change
order.canceledAn order is canceled
product.updatedA product changes, or its photos finish copying
product.deletedA product is archived
inventory.lowA variant reaches its low-stock level
job.completedA bulk job finishes
store.suspendedThe store closes to shoppers, e.g. an unpaid plan
store.reactivatedThe store opens again
connection.revokedThe owner disconnects your app — always sent
customer.redactA shopper asks for their data to be deleted
Every delivery is signed with your webhook secret. Check the signature before trusting anything in it — anyone can post to your URL.
import crypto from "node:crypto";
// Use the raw body, before any JSON parsing.
function isFromShopZen(rawBody, headers, secret) {
const timestamp = headers["shopzen-timestamp"];
const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const signatures = (headers["shopzen-signature"] ?? "").split(",").map((s) => s.trim().replace(/^v1=/, ""));
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
return fresh && signatures.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}- Failed deliveries are retried for up to 3 days, with growing gaps.
- The same event can arrive twice, and out of order. Skip duplicates by
idand compareupdated_at. - Webhooks are a signal, not a guarantee: sync with
updated_sinceevery hour as a backup.
Errors
Match on the error code, never the message. Include request_id when you contact us.
{
"error": {
"code": "validation_failed",
"message": "variants[0].price must be a positive integer",
"details": { "field": "variants[0].price" },
"request_id": "req_01J8ZA1…"
}
}400validation_failed
Fix the request — details names the field.
401token_expired
Refresh the access token and retry.
401invalid_token
Revoked or wrong. If refreshing fails too, ask the user to connect again.
403insufficient_scope
The connection lacks a permission. Ask the user to reconnect with it.
403store_suspended
Reads still work. Retry writes after store.reactivated.
403plan_limit_reached
The store's plan is full. Tell the user; they can upgrade in ShopZen.
409conflict
A SKU or external_id is already used by another record.
429rate_limited
Wait Retry-After seconds, then retry.
5xxserver_error
Our side. Retry with backoff and the same Idempotency-Key.
Retry 429 and 5xx with exponential backoff (1 s, 2 s, 4 s… up to a minute). Don't retry other 4xx errors without fixing the request.
Going live
Before ShopZen issues a production key, your integration shows us that it:
- The client secret and webhook secret live only on your server
- The callback checks state, and the connect flow uses PKCE
- The new refresh token is saved after every refresh, one refresh at a time per store
- Every webhook signature and timestamp is checked, and duplicates are skipped
- 401, 403 and 429 errors show the user a clear message
- Every POST sends an Idempotency-Key
- Users can disconnect from your app, and connection.revoked is handled
- A store's customer data is deleted on disconnect or customer.redact
- Your users have a support contact they can reach
Technical help: [email protected]. Security issues: security@shopzen.bd — please don't post them publicly.
Questions
Can I create an API key myself?
No. There is no self-serve key page. Every key is created by a ShopZen administrator after reviewing your application, and ShopZen can rotate or turn off a key at any time. Store owners can't create keys either — they approve your app on ShopZen's own Connect screen.
Is there a fee for partners?
No. Each connected store pays its normal ShopZen plan, and that's all.
What if my client secret leaks?
Email [email protected] straight away and we rotate it. Nobody from ShopZen will ever ask you for your secret.
Can the API change a store's design, domain or billing?
No. Store design, pages, domains, discount codes, team and billing stay in the ShopZen dashboard, where only the store's own people can change them.
Will v1 change under me?
Only in ways that don't break integrations: new endpoints, new optional fields and new events. A breaking change means /partner/v2, and v1 keeps working for at least 12 months after.