Apps API
Build integrations with Zapier, Make.com, and custom apps using the Cartly REST Apps API and REST Hooks.
Overview
The Cartly Apps API powers official integrations like Zapier and Make.com and is available to any OAuth 2.0 authorized app. Two interaction patterns:
- REST Hook triggers — subscribe a URL for push-based real-time events (used by Zapier and Make)
- Action endpoints — create customers, update orders, adjust inventory, create discounts
Authentication
All Apps API requests require an OAuth 2.0 access token in the Authorization: Bearer header. Complete the OAuth 2.0 flow to obtain a token.
GET /apps/api/me
curl "https://cartly.pro/apps/api/me" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Response
# { "id": "...", "name": "My Store", "domain": "my-store.mycartly.pro", "email": "owner@example.com", "currency": "USD", "plan": "growth" }REST Hook Events (Triggers)
Subscribe a URL to receive push-based real-time events. Zapier and Make.com use this for instant triggers. Pass your own secret when you register a hook: without it you cannot verify signatures.
| Name | Type | Required | Description |
|---|---|---|---|
| order.created | event | No | Fired when a new order is placed |
| order.cancelled | event | No | Fired when an order is cancelled |
| order.fulfilled | event | No | Fired when order is fulfilled |
| product.created | event | No | Fired when a new product is added |
| product.updated | event | No | Fired when product details change |
| customer.created | event | No | Fired when a customer account is created through the Headless Storefront API or the Apps API |
| cart.abandoned | event | No | Fired when cart is abandoned after configured delay |
Register and unregister REST Hooks
# Register a REST Hook (pass your own secret: you need it to verify signatures)
curl -X POST "https://cartly.pro/apps/api/webhooks" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://hooks.zapier.com/...", "events": ["order.created"], "secret": "YOUR_SIGNING_SECRET"}'
# Unregister
curl -X DELETE "https://cartly.pro/apps/api/webhooks/WEBHOOK_ID" \
-H "Authorization: Bearer ACCESS_TOKEN"API Endpoints
Available REST endpoints for app integrations.
| Name | Type | Required | Description |
|---|---|---|---|
| GET /apps/api/me | endpoint | No | Returns the authenticated shop: id, name, domain, email, currency, plan |
| GET /apps/api/customers | endpoint | No | List customers with optional email filter |
| POST /apps/api/customers | endpoint | No | Create a new customer |
| PUT /apps/api/customers/:id | endpoint | No | Update customer fields |
| PUT /apps/api/orders/:id | endpoint | No | Update an order's fulfillment: fulfillment_status = fulfilled | partial | unfulfilled (omit to leave it unchanged; any other value is a 400), tracking_number, tracking_company, tracking_url (stored only together with a tracking_number or tracking_company); a note field is accepted but not stored. Marking a Klarna order fulfilled captures its payment first — a failed capture is a 422 and the fulfillment status is unchanged (retry) |
| PUT /apps/api/inventory/:variant_id | endpoint | No | Set the available quantity of a variant at a location: available is required (missing → 400, never read as 0); default location unless location_id (a shop with no active location → 409); a location that doesn't stock the variant → 404 |
| POST /apps/api/discounts | endpoint | No | Create a discount code |
| GET /apps/api/webhooks | endpoint | No | List registered REST Hook subscriptions |
| POST /apps/api/webhooks | endpoint | No | Register a new REST Hook URL |
| DELETE /apps/api/webhooks/:id | endpoint | No | Unregister a REST Hook |
Action Examples
curl -X POST "https://cartly.pro/apps/api/customers" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
--data '{"email": "jane@example.com", "first_name": "Jane"}'Webhook Headers
Every REST Hook delivery includes:
X-Cartly-Hmac-Sha256— HMAC-SHA256 signature of the raw body, prefixed withsha256=(deprecated: no replay protection)X-Cartly-Signature-V2—t=<unix seconds>,v1=<hex>, HMAC-SHA256 oft + "." + raw body— verify this one and reject stale timestampsX-Cartly-Event-Id— the event's id, the same on every delivery of it: dedupe on itX-Cartly-Shop-Domain— the shop storefront domain (e.g.mystore.mycartly.pro)X-Cartly-Webhook-Id— unique ID of the webhook subscription
Zapier Integration
Install from the Cartly App Store. Automate with 6,000+ apps — no code required.
- Triggers (7): New Order, Order Updated, Order Fulfilled, Order Canceled, New Product, Product Updated, New Customer
- Actions (4): Create Customer, Update Order, Adjust Inventory, Create Discount
- Searches (2): Find Customer by email, Find Order by number
Make.com Integration
Available on Make.com. Build multi-step scenarios with full data mapping.
- Instant Triggers (6): New Orders, Order Updates, New Products, Product Updates, New Customers, Abandoned Carts
- Actions (4): Create Customer, Update Order, Adjust Inventory, Create Discount
- Searches (2): Search Customer, Search Order
OAuth Consent Page
For browser-redirect flows (required by Zapier and Make.com), redirect merchants to the Cartly OAuth consent page. See the Authentication guide for the full flow.
Signature verification (Node.js)
import crypto from "crypto";
// X-Cartly-Signature-V2: "t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + raw body)>"
function verifyCartlyWebhook(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
const got = Buffer.from(parts.v1 || "", "hex");
const want = Buffer.from(expected, "hex");
return got.length === want.length && crypto.timingSafeEqual(got, want);
}
// Express handler — verify the RAW body
app.post("/cartly-hook",
express.raw({ type: "application/json" }),
(req, res) => {
const header = req.headers["x-cartly-signature-v2"] || "";
if (!verifyCartlyWebhook(req.body.toString("utf8"), header, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
// dedupe on req.headers["x-cartly-event-id"]
res.status(200).json({ received: true });
}
);When a Store Uninstalls Your App
The app's access tokens are revoked and its REST hooks stop receiving events right away. A hook subscribed to app/uninstalled still receives that one event and is then removed. See the Webhooks guide.