Getting started
- Ask a store admin to open Dashboard → Settings → Integrations and create a connection for your system.
- Generate an API key and copy the webhook signing secret (shown once).
- Provide your HTTPS webhook URL and enable the events you need.
- Call the REST API with
Authorization: Bearer <apiKey>.
Per-system keys
Each integration has its own API key and webhook URL.
Push + pull
Webhooks notify you of changes; REST lets you sync on demand.
Signed payloads
Verify X-FxPrint-Signature with HMAC-SHA256.
Base URL: https://fxprintshop.com
Authentication
All /api/integrations/* routes require a Bearer token. The connection must be enabled and have an API key generated.
Authorization: Bearer fxp_xxxxxxxxxxxxxxxx- Do not put the API key in query strings or client-side JavaScript.
- Rotate the key from the dashboard if it is leaked.
- Disabled connections reject requests even with a previously valid key.
API conventions
List query parameters
limit— 1–200 (default 50)skip— pagination offset (default 0)updatedSince— ISO-8601 timestamp; matchesupdatedAtorcreatedAtlowStock=true— only on/stock
List response shape
{
"data": [ /* array of records */ ],
"meta": {
"total": 123,
"limit": 50,
"skip": 0
}
}Detail response shape
{ "data": { /* single record */ } }Dates are serialized as ISO strings. MongoDB _id values are returned as strings.
curl -sS -H "Authorization: Bearer fxp_YOUR_API_KEY" \
"https://fxprintshop.com/api/integrations/orders?limit=20&skip=0"REST endpoints
Read-only in the current version. There are no public create/update/delete endpoints.
| Method | Path | Description | ID lookup |
|---|---|---|---|
| GET | /api/integrations/orders | List orders | — |
| GET | /api/integrations/orders/{id} | Single order | _id or orderNumber |
| GET | /api/integrations/customers | List customers | — |
| GET | /api/integrations/customers/{id} | Single customer | _id or customerId |
| GET | /api/integrations/products | List products | — |
| GET | /api/integrations/products/{id} | Single product | _id, productId, or slug |
| GET | /api/integrations/categories | List categories | — |
| GET | /api/integrations/categories/{id} | Single category | _id or categoryId |
| GET | /api/integrations/brands | List brands | — |
| GET | /api/integrations/brands/{id} | Single brand | _id or brandId |
| GET | /api/integrations/shipping-methods | List shipping methods | — |
| GET | /api/integrations/shipping-methods/{id} | Single method | _id or shippingMethodId |
| GET | /api/integrations/stock | Stock snapshot (?lowStock=true) | — |
| GET | /api/integrations/stock/{id} | Stock + history | _id, productId, or slug |
| GET | /api/integrations/promotions | List promotions | — |
| GET | /api/integrations/promotions/{id} | Single promotion | _id, promotionId, or code |
| GET | /api/integrations/returns | List returns | — |
| GET | /api/integrations/returns/{id} | Single return | _id or returnRequestId |
| GET | /api/integrations/service-requests | List service requests | — |
| GET | /api/integrations/service-requests/{id} | Single request | _id or serviceRequestId |
| GET | /api/integrations/webhook-logs | Delivery log (recent) | — |
Example payloads
Every list endpoint returns { data, meta }. Every detail endpoint returns { data }. Below are representative records for each resource.
Orders
GET /api/integrations/orders · GET /api/integrations/orders/{id}
{
"data": [
{
"_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"orderNumber": "ORD-1001",
"customer": {
"fullName": "Jane Doe",
"email": "jane@example.com",
"firebaseUid": "firebase_uid_abc",
"customerId": "CUST-1001"
},
"shippingAddress": {
"fullName": "Jane Doe",
"mobile": "+201000000000",
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo",
"isPrimary": true
},
"items": [
{
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"sku": "PP-810",
"price": 120,
"quantity": 2,
"productType": "Custom Prints",
"product": { "image": "https://cdn.example.com/p1.jpg" },
"variationId": "var_1",
"variationDetails": {
"variationId": "var_1",
"variationSku": "PP-810-GLOSS",
"variationPrice": 120,
"options": [
{ "typeName": "Finish", "optionValue": "Glossy", "optionName": "Glossy" }
]
}
}
],
"subtotal": 240,
"shippingCost": 40,
"giftWrapping": false,
"giftWrappingCost": 0,
"discountAmount": 0,
"vatAmount": 0,
"vatRate": 0,
"vatCalculationMethod": "exclusive",
"total": 280,
"shippingMethod": {
"_id": "66f1ship0000000000000001",
"name": "Standard Delivery",
"price": 40,
"shippingMethodId": "SHIP-1001",
"estimatedDelivery": "2-4 business days"
},
"status": "Pending",
"paymentMethod": "Cash on Delivery",
"paymentStatus": "Unpaid",
"orderNote": "",
"statusHistory": [
{
"status": "Pending",
"changedAt": "2026-09-07T12:00:00.000Z",
"comment": "Order created"
}
],
"hasRefund": false,
"createdAt": "2026-09-07T12:00:00.000Z",
"updatedAt": "2026-09-07T12:00:00.000Z"
}
],
"meta": {
"total": 1,
"limit": 50,
"skip": 0
}
}{
"data": {
"_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"orderNumber": "ORD-1001",
"customer": {
"fullName": "Jane Doe",
"email": "jane@example.com",
"firebaseUid": "firebase_uid_abc",
"customerId": "CUST-1001"
},
"shippingAddress": {
"fullName": "Jane Doe",
"mobile": "+201000000000",
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo",
"isPrimary": true
},
"items": [
{
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"sku": "PP-810",
"price": 120,
"quantity": 2,
"productType": "Custom Prints",
"product": { "image": "https://cdn.example.com/p1.jpg" },
"variationId": "var_1",
"variationDetails": {
"variationId": "var_1",
"variationSku": "PP-810-GLOSS",
"variationPrice": 120,
"options": [
{ "typeName": "Finish", "optionValue": "Glossy", "optionName": "Glossy" }
]
}
}
],
"subtotal": 240,
"shippingCost": 40,
"giftWrapping": false,
"giftWrappingCost": 0,
"discountAmount": 0,
"vatAmount": 0,
"vatRate": 0,
"vatCalculationMethod": "exclusive",
"total": 280,
"shippingMethod": {
"_id": "66f1ship0000000000000001",
"name": "Standard Delivery",
"price": 40,
"shippingMethodId": "SHIP-1001",
"estimatedDelivery": "2-4 business days"
},
"status": "Pending",
"paymentMethod": "Cash on Delivery",
"paymentStatus": "Unpaid",
"orderNote": "",
"statusHistory": [
{
"status": "Pending",
"changedAt": "2026-09-07T12:00:00.000Z",
"comment": "Order created"
}
],
"hasRefund": false,
"createdAt": "2026-09-07T12:00:00.000Z",
"updatedAt": "2026-09-07T12:00:00.000Z"
}
}Customers
GET /api/integrations/customers · GET /api/integrations/customers/{id}
{
"data": [
{
"_id": "66f1cust0000000000000001",
"customerId": "CUST-1001",
"firebaseUid": "firebase_uid_abc",
"fullName": "Jane Doe",
"email": "jane@example.com",
"status": "active",
"addresses": [
{
"fullName": "Jane Doe",
"mobile": "+201000000000",
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo",
"isPrimary": true
}
],
"wishlist": ["photo-print-8x10"],
"images": [],
"createdAt": "2026-09-01T10:00:00.000Z"
}
],
"meta": {
"total": 1,
"limit": 50,
"skip": 0
}
}{
"data": {
"_id": "66f1cust0000000000000001",
"customerId": "CUST-1001",
"firebaseUid": "firebase_uid_abc",
"fullName": "Jane Doe",
"email": "jane@example.com",
"status": "active",
"addresses": [
{
"fullName": "Jane Doe",
"mobile": "+201000000000",
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo",
"isPrimary": true
}
],
"wishlist": ["photo-print-8x10"],
"images": [],
"createdAt": "2026-09-01T10:00:00.000Z"
}
}Products
GET /api/integrations/products · GET /api/integrations/products/{id}
{
"data": [
{
"_id": "66f1prod0000000000000001",
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"slug": "photo-print-8x10",
"htmlDescription": "<p>High quality photo print</p>",
"sku": "PP-810",
"productType": "Custom Prints",
"basePrice": 120,
"salePrice": null,
"categoryIds": ["66f1cat0000000000000001"],
"brandId": "66f1brand000000000000001",
"images": ["https://cdn.example.com/p1.jpg"],
"tags": ["print", "photo"],
"status": "Published",
"trackStock": true,
"allowBackorders": false,
"stockQuantity": 50,
"lowStockThreshold": 10,
"hasVariations": true,
"variationTypes": [
{
"_id": "vt1",
"name": "Finish",
"displayName": "Finish",
"isRequired": true,
"order": 0,
"options": [
{
"_id": "opt1",
"name": "Glossy",
"value": "Glossy",
"priceAdjustment": 0,
"stockQuantity": 30,
"isActive": true
}
]
}
],
"variations": [
{
"_id": "var_1",
"variationId": "var_1",
"options": [{ "typeName": "Finish", "optionValue": "Glossy" }],
"price": 120,
"sku": "PP-810-GLOSS",
"stockQuantity": 30,
"isActive": true
}
],
"createdAt": "2026-08-15T09:00:00.000Z"
}
],
"meta": {
"total": 1,
"limit": 50,
"skip": 0
}
}{
"data": {
"_id": "66f1prod0000000000000001",
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"slug": "photo-print-8x10",
"htmlDescription": "<p>High quality photo print</p>",
"sku": "PP-810",
"productType": "Custom Prints",
"basePrice": 120,
"salePrice": null,
"categoryIds": ["66f1cat0000000000000001"],
"brandId": "66f1brand000000000000001",
"images": ["https://cdn.example.com/p1.jpg"],
"tags": ["print", "photo"],
"status": "Published",
"trackStock": true,
"allowBackorders": false,
"stockQuantity": 50,
"lowStockThreshold": 10,
"hasVariations": true,
"variationTypes": [
{
"_id": "vt1",
"name": "Finish",
"displayName": "Finish",
"isRequired": true,
"order": 0,
"options": [
{
"_id": "opt1",
"name": "Glossy",
"value": "Glossy",
"priceAdjustment": 0,
"stockQuantity": 30,
"isActive": true
}
]
}
],
"variations": [
{
"_id": "var_1",
"variationId": "var_1",
"options": [{ "typeName": "Finish", "optionValue": "Glossy" }],
"price": 120,
"sku": "PP-810-GLOSS",
"stockQuantity": 30,
"isActive": true
}
],
"createdAt": "2026-08-15T09:00:00.000Z"
}
}Categories
{
"data": {
"_id": "66f1cat0000000000000001",
"categoryId": "CAT-1001",
"name": "Photo Prints",
"description": "Printed photos",
"parentId": null,
"image": "https://cdn.example.com/cat.jpg",
"isActive": true,
"createdAt": "2026-08-01T08:00:00.000Z"
}
}Brands
{
"data": {
"_id": "66f1brand000000000000001",
"brandId": "BRAND-1001",
"name": "FxPrint",
"description": "House brand",
"logo": "https://cdn.example.com/logo.png",
"isActive": true,
"createdAt": "2026-08-01T08:00:00.000Z"
}
}Shipping methods
{
"data": {
"_id": "66f1ship0000000000000001",
"shippingMethodId": "SHIP-1001",
"name": "Standard Delivery",
"price": 40,
"estimatedDelivery": "2-4 business days",
"createdAt": "2026-08-01T08:00:00.000Z"
}
}Stock
GET /api/integrations/stock returns stock fields only. GET /api/integrations/stock/{id} adds recent history.
{
"data": [
{
"_id": "66f1prod0000000000000001",
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"slug": "photo-print-8x10",
"trackStock": true,
"allowBackorders": false,
"stockQuantity": 50,
"lowStockThreshold": 10,
"createdAt": "2026-08-15T09:00:00.000Z",
"updatedAt": "2026-09-07T11:00:00.000Z",
"variations": [
{
"_id": "var_1",
"sku": "PP-810-GLOSS",
"name": "Glossy",
"stockQuantity": 30
}
]
}
],
"meta": {
"total": 1,
"limit": 50,
"skip": 0
}
}{
"data": {
"_id": "66f1prod0000000000000001",
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"slug": "photo-print-8x10",
"trackStock": true,
"allowBackorders": false,
"stockQuantity": 50,
"lowStockThreshold": 10,
"variations": [
{
"_id": "var_1",
"sku": "PP-810-GLOSS",
"name": "Glossy",
"stockQuantity": 30
}
],
"history": [
{
"_id": "66f1hist0000000000000001",
"productId": "PROD-1001",
"productName": "Photo Print 8x10",
"variationId": "var_1",
"previousStock": 32,
"newStock": 30,
"change": -2,
"type": "remove",
"reason": "Order ORD-1001",
"userId": "admin_uid",
"userName": "Admin",
"timestamp": "2026-09-07T12:05:00.000Z"
}
]
}
}Promotions
{
"data": {
"_id": "66f1promo000000000000001",
"promotionId": "PROMO-1001",
"code": "SAVE10",
"description": "10% off",
"discountType": "percentage",
"discountValue": 10,
"isActive": true,
"startDate": "2026-09-01T00:00:00.000Z",
"endDate": "2026-09-30T23:59:59.000Z",
"productTags": ["print"],
"createdAt": "2026-08-20T10:00:00.000Z"
}
}Returns
{
"data": {
"_id": "66f1ret00000000000000001",
"returnRequestId": "RET-1001",
"orderId": "66f1a2b3c4d5e6f7a8b9c0d1",
"orderNumber": "ORD-1001",
"customerName": "Jane Doe",
"customerEmail": "jane@example.com",
"customerId": "firebase_uid_abc",
"items": [
{
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"sku": "PP-810",
"quantity": 1,
"reason": "Damaged in shipping"
}
],
"status": "Pending",
"refundAmount": 120,
"refundMethod": "Credit Card",
"refundStatus": "Pending",
"createdAt": "2026-09-08T09:00:00.000Z",
"updatedAt": "2026-09-08T09:00:00.000Z"
}
}Service requests
{
"data": {
"_id": "66f1srv00000000000000001",
"serviceRequestId": "Req-1001",
"customerName": "Jane Doe",
"companyName": "Acme Co",
"phoneNumber": "+201000000000",
"email": "jane@example.com",
"customerId": "firebase_uid_abc",
"serviceTypeId": "66f1stype000000000000001",
"serviceTypeName": "On-site Printing",
"preferredDateTime": "2026-09-15T14:00:00.000Z",
"notes": "Bring samples",
"serviceAddress": {
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo"
},
"locationPin": { "lat": 30.0444, "lng": 31.2357 },
"cost": 500,
"status": "New",
"replies": [],
"createdAt": "2026-09-07T16:00:00.000Z"
}
}Webhook logs
{
"data": [
{
"_id": "66f1log00000000000000001",
"event": "order.created",
"success": true,
"statusCode": 200,
"webhookUrl": "https://partner.example.com/hooks/fxprint",
"connectionId": "int_abc123",
"connectionName": "Your ERP",
"createdAt": "2026-09-07T12:00:01.000Z"
}
],
"meta": {
"total": 1,
"limit": 50,
"skip": 0
}
}Webhooks
When an enabled event occurs, Fx Print Shop sends POST to your webhook URL for that connection. Failed deliveries are logged in the dashboard; the store action is not rolled back if your endpoint fails.
Request headers
Content-Type: application/jsonX-FxPrint-Event— event nameX-FxPrint-Connection-Id— connection idX-FxPrint-Signature— HMAC-SHA256 hex of the raw body (when a signing secret exists)User-Agent: FxPrint-Webhooks/1.0
Events
| Event | Triggered when |
|---|---|
| order.created | Order placed (non–credit-card) or after successful card payment |
| order.updated | Admin status change or payment status update |
| customer.created | Customer signup or admin creates a customer |
| customer.updated | Profile or admin customer update |
| product.created | Admin creates a product |
| product.updated | Admin updates a product |
| integration.test | Manual test from the store dashboard |
Example payloads by event
Envelope is always the same; data matches the REST resource document for that entity.
{
"event": "order.created",
"timestamp": "2026-09-07T12:00:00.000Z",
"connectionId": "int_abc123",
"connectionName": "Your ERP",
"data": {
"_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"orderNumber": "ORD-1001",
"customer": {
"fullName": "Jane Doe",
"email": "jane@example.com",
"firebaseUid": "firebase_uid_abc",
"customerId": "CUST-1001"
},
"shippingAddress": {
"fullName": "Jane Doe",
"mobile": "+201000000000",
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo",
"isPrimary": true
},
"items": [
{
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"sku": "PP-810",
"price": 120,
"quantity": 2,
"productType": "Custom Prints",
"product": { "image": "https://cdn.example.com/p1.jpg" },
"variationId": "var_1",
"variationDetails": {
"variationId": "var_1",
"variationSku": "PP-810-GLOSS",
"variationPrice": 120,
"options": [
{ "typeName": "Finish", "optionValue": "Glossy", "optionName": "Glossy" }
]
}
}
],
"subtotal": 240,
"shippingCost": 40,
"giftWrapping": false,
"giftWrappingCost": 0,
"discountAmount": 0,
"vatAmount": 0,
"vatRate": 0,
"vatCalculationMethod": "exclusive",
"total": 280,
"shippingMethod": {
"_id": "66f1ship0000000000000001",
"name": "Standard Delivery",
"price": 40,
"shippingMethodId": "SHIP-1001",
"estimatedDelivery": "2-4 business days"
},
"status": "Pending",
"paymentMethod": "Cash on Delivery",
"paymentStatus": "Unpaid",
"orderNote": "",
"statusHistory": [
{
"status": "Pending",
"changedAt": "2026-09-07T12:00:00.000Z",
"comment": "Order created"
}
],
"hasRefund": false,
"createdAt": "2026-09-07T12:00:00.000Z",
"updatedAt": "2026-09-07T12:00:00.000Z"
}
}{
"event": "customer.created",
"timestamp": "2026-09-07T12:00:00.000Z",
"connectionId": "int_abc123",
"connectionName": "Your ERP",
"data": {
"_id": "66f1cust0000000000000001",
"customerId": "CUST-1001",
"firebaseUid": "firebase_uid_abc",
"fullName": "Jane Doe",
"email": "jane@example.com",
"status": "active",
"addresses": [
{
"fullName": "Jane Doe",
"mobile": "+201000000000",
"street": "12 Nile St",
"landmark": "Near metro",
"city": "Nasr City",
"governorate": "Cairo",
"isPrimary": true
}
],
"wishlist": ["photo-print-8x10"],
"images": [],
"createdAt": "2026-09-01T10:00:00.000Z"
}
}{
"event": "product.created",
"timestamp": "2026-09-07T12:00:00.000Z",
"connectionId": "int_abc123",
"connectionName": "Your ERP",
"data": {
"_id": "66f1prod0000000000000001",
"productId": "PROD-1001",
"name": "Photo Print 8x10",
"slug": "photo-print-8x10",
"htmlDescription": "<p>High quality photo print</p>",
"sku": "PP-810",
"productType": "Custom Prints",
"basePrice": 120,
"salePrice": null,
"categoryIds": ["66f1cat0000000000000001"],
"brandId": "66f1brand000000000000001",
"images": ["https://cdn.example.com/p1.jpg"],
"tags": ["print", "photo"],
"status": "Published",
"trackStock": true,
"allowBackorders": false,
"stockQuantity": 50,
"lowStockThreshold": 10,
"hasVariations": true,
"variationTypes": [
{
"_id": "vt1",
"name": "Finish",
"displayName": "Finish",
"isRequired": true,
"order": 0,
"options": [
{
"_id": "opt1",
"name": "Glossy",
"value": "Glossy",
"priceAdjustment": 0,
"stockQuantity": 30,
"isActive": true
}
]
}
],
"variations": [
{
"_id": "var_1",
"variationId": "var_1",
"options": [{ "typeName": "Finish", "optionValue": "Glossy" }],
"price": 120,
"sku": "PP-810-GLOSS",
"stockQuantity": 30,
"isActive": true
}
],
"createdAt": "2026-08-15T09:00:00.000Z"
}
}{
"event": "integration.test",
"timestamp": "2026-09-07T12:00:00.000Z",
"connectionId": "int_abc123",
"connectionName": "Your ERP",
"data": {
"message": "This is a test webhook from FxPrint",
"testedAt": "2026-09-07T12:00:00.000Z",
"connectionId": "int_abc123",
"connectionName": "Your ERP"
}
}Respond with 2xx quickly. Do heavy work asynchronously on your side.
Verify signatures
Always verify X-FxPrint-Signature using the webhook signing secret (not the API key). Hash the exact raw body bytes with HMAC-SHA256 and compare in constant time.
import crypto from 'crypto';
/**
* Verify X-FxPrint-Signature (HMAC-SHA256 of the raw request body).
* Use the webhook signing secret shown once when the API key was generated.
*/
export function verifyFxPrintSignature(
rawBody: string | Buffer,
signatureHeader: string | null,
webhookSecret: string
): boolean {
if (!signatureHeader) return false;
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody)
.digest('hex');
try {
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(signatureHeader, 'hex');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
} catch {
return false;
}
}Data models (field reference)
Webhook data and REST responses use the store's Mongo documents. Important fields:
Order
orderNumber,status,paymentStatus,paymentMethodcustomer—fullName,email,firebaseUid?,customerId?shippingAddress,items[], totals (subtotal,shippingCost,vat*,total)statusHistory[],createdAt,updatedAt
Customer
customerId,fullName,email,status(active|inactive)addresses[]— fullName, mobile, street, landmark?, city, governorate, isPrimaryfirebaseUid?,wishlist[],createdAt
Product
productId,name,slug,basePrice,salePrice?categoryIds[],brandId?,images[],tags[]- Stock:
trackStock,stockQuantity,lowStockThreshold,allowBackorders - Variations:
hasVariations,variationTypes[],variations[](price, sku, stockQuantity)
Other resources
- Category — categoryId, name, parentId?, isActive
- Brand — brandId, name, logo?, isActive
- Shipping method — shippingMethodId, name, price, estimatedDelivery
- Promotion — promotionId, code, discountType (percentage|fixed), discountValue, startDate, endDate, isActive
- Return — returnRequestId, orderNumber, status (Pending|Approved|Rejected|Processing|Completed), refundStatus, items[]
- Service request — serviceRequestId, status (New|In Progress|Completed|Cancelled), serviceTypeName, customer contact, address
Errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid Authorization Bearer token |
| 403 | No enabled connection with an API key |
| 404 | Resource not found |
| 500 | Server or database error |
{ "error": "Invalid API key" }Best practices
- Idempotently handle webhooks using event + resource id (retries may occur later).
- Prefer
updatedSincepolling for catch-up, plus webhooks for near-real-time updates. - Store secrets in a vault; never commit API keys or webhook secrets.
- Use HTTPS webhook endpoints only.
- Contact the store admin if you need additional events or write access — not available in the public API yet.
Contact the store if you need an integration provisioned.
