Skip to content
Back to Help Desk
DevelopersOctober 31, 2026

Webhooks and integrations API (Custom Store)

Push products, customers and orders from your own website into EcomUp with an API key, and receive signed order webhooks.

Connect your own website to EcomUp with the Custom Store integration. Your site sends products, categories, customers, addresses and orders into EcomUp through a simple API. EcomUp sends events back to your site through a webhook (an automatic message to a URL you choose) when orders are created or change status. This guide is written for the developer who builds your website.

Before you start

  • Your role needs permission to manage connections (owners and admins have it by default).
  • Your website needs a server that can send HTTPS requests and, for webhooks, a public HTTPS URL that can receive them.
  • Read Developer overview if you're not sure Custom Store is the right fit. WooCommerce stores have their own no-code connection.

Steps

  1. In the left menu, open Connections, find Custom Store, and click Connect.
  2. Click Connect Custom Store.
  3. Copy the API key shown under API key — send as x-api-key header. It's shown only once.
    Custom Store Inbound API section
    Copy your API key (1) right away (Regenerate (2) makes a new one); the endpoints are listed below (3).
  4. Copy the endpoint URLs from the Inbound — API list. Each one has a copy button in the documentation panel.
  5. Under Outbound — Order Webhook, type your URL in Your API endpoint (for example https://yourstore.com/api/webhooks/orders) and click Save Webhook.
  6. Click the eye icon next to Signing secret and copy it. Your server uses it to check that webhooks really come from EcomUp.
    Outbound Order Webhook section
    Type Your API endpoint (1), click Save Webhook (2), and copy the Signing secret with the eye icon (3).
  7. Send a test product from your server, then check it appears in Products.

Tips

  • Push in this order: categories and products first, then customers and addresses, then orders. Order line items link to products by externalProductId.
  • externalId is your own ID for each record. EcomUp uses it to avoid duplicates, so keep it stable.
  • Always send "currency": "BDT" (or your store currency) with orders. If you leave it out, the order is saved in USD.
  • Send prices as strings for products ("49.99") and as numbers for order line items (49.99).
  • Unknown fields are rejected. Only send the fields listed below.
  • Webhook deliveries are not retried, and EcomUp waits up to 10 seconds for your reply. Return a 2xx response quickly and do slow work afterwards.
  • Click Regenerate if your key leaks. The old key stops working immediately.

Troubleshooting

“Missing x-api-key header” or “Invalid API key” (401)

Send the key in a header named exactly x-api-key. If you regenerated the key or disconnected the Custom Store, update your server with the new key.

“price must be a non-negative number (e.g. 10 or 10.99)” (400)

Product prices must be strings of digits with an optional decimal point, with no currency symbol or commas.

“property … should not exist” (400)

You sent a field EcomUp doesn't accept. Remove it and try again.

“customerExternalId not found — create the customer first via POST /webhook/custom-store/customers” (400)

Create the customer before you add an address for them.

“Provide either customer or customerId” (400)

Every order needs a customer. Send an inline customer object, or the customerId returned by the customers endpoint.

Order items show without a product link

A line item's externalProductId didn't match any product you pushed. Push that product first, using the same ID as its externalId.

I'm not receiving webhooks

Check the URL is public, uses HTTPS, and replies within 10 seconds. Note that order.created is sent for orders created in the EcomUp dashboard or by the AI agent, not for orders you pushed in yourself (to avoid echoing your own data back).

“Invalid signature” on my server

Compute the HMAC over the raw request body exactly as received, before any JSON parsing, and use the current signing secret.

For developers

Authentication and base URL

All inbound requests are POST with a JSON body and two headers. Copy the full URL for each endpoint from the Custom Store page; it looks like https://api.ecomup.ai/webhook/custom-store/products.

POST /webhook/custom-store/{resource}
Content-Type: application/json
x-api-key: YOUR_API_KEY

Endpoints

EndpointWhat it doesResponse
/webhook/custom-store/productsCreate or update a product (matched by externalId){ "success": true, "product": { … } }
/webhook/custom-store/categoriesFind or create a category by name{ "success": true, "category": { "id", "name", "slug" } }
/webhook/custom-store/customersFind or create a customer{ "success": true, "customerId": "…" }
/webhook/custom-store/addressesAdd a shipping address to a customer{ "success": true, "addressId": "…" }
/webhook/custom-store/ordersCreate an order with line items, customer and address{ "success": true }

Products

FieldTypeRequiredNotes
externalIdstringYesYour product ID (dedupe key), max 255
namestringYesMax 255
pricestring (decimal)YesCurrent selling price, e.g. "1250" or "49.99"
skustringNo
regularPricestring (decimal)NoDefaults to price
salePricestring (decimal)No
description, shortDescriptionstringNo
imageUrlstring (URL)NoMain image
imagesstring[] (URLs)NoGallery
stockQuantitynumberNo
stockStatusenumNoinstock, outofstock, onbackorder
statusenumNodraft, publish, pending
categorystringNoCategory name; created if it doesn't exist
curl -X POST https://api.ecomup.ai/webhook/custom-store/products \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "sku-123",
    "name": "Cotton Panjabi",
    "price": "1250",
    "regularPrice": "1500",
    "category": "Panjabi",
    "imageUrl": "https://cdn.example.com/panjabi.jpg",
    "stockQuantity": 20
  }'

Categories

FieldTypeRequiredNotes
namestringYesFound or created; safe to send again

Customers

FieldTypeRequiredNotes
externalIdstringYesYour customer ID (dedupe key)
firstNamestringYes
lastNamestringNo
phonestringNo

Addresses

FieldTypeRequiredNotes
customerExternalIdstringYesThe customer must already exist
addressLine1stringYesMax 255
citystringYesMax 60
addressLine2stringNoMax 255
statestringNoMax 60
postalCodestringNoMax 20
countrystringNoMax 40
phonestringNoMax 20

Orders

FieldTypeRequiredNotes
externalIdstringYesYour order ID (dedupe key)
statusenumNopending (default), processing, on-hold, completed, cancelled, refunded, failed, paid
paymentMethodstringNoe.g. cod
paymentStatusstringNoDefaults to pending
currencystringNo3-letter code. Defaults to USD, so send BDT
customerIduuidOne of theseID returned by /customers
customerobjectOne of theseexternalId, firstName required; lastName, phone optional
shippingAddressIduuidNoID returned by /addresses; wins over address
addressobjectNoSame fields as the Addresses endpoint, without customerExternalId
lineItemsarray (min 1)YesEach: externalProductId (string), quantity (number, ≥ 1), price (number, unit price ≥ 0), optional productName
customerNotestringNo
{
  "externalId": "order-001",
  "status": "pending",
  "currency": "BDT",
  "paymentMethod": "cod",
  "customer": { "externalId": "cust-123", "firstName": "Rahim", "phone": "01712345678" },
  "address": { "addressLine1": "House 12, Road 5, Dhanmondi", "city": "Dhaka" },
  "lineItems": [{ "externalProductId": "sku-123", "quantity": 2, "price": 1250 }]
}

Outbound webhook events

EcomUp sends a POST with Content-Type: application/json to the URL you saved.

EventSent whenData key
order.createdAn order is created in the EcomUp dashboard (including duplicated orders) or by the AI sales agentorder
order.updatedAn order's status changes in EcomUporder
product.createdA product is created in EcomUp (dashboard or AI)product
customer.createdA customer is created in EcomUpcustomer
category.createdA category is created in EcomUpcategory

Data you push in through the inbound API is not sent back to you. Order events include the full order with its line items, customer and shipping address.

POST https://yourstore.com/api/webhooks/orders
Content-Type: application/json
X-EcomUp-Signature: sha256=5f2b…

{
  "event": "order.updated",
  "order": {
    "id": "…",
    "orderNumber": 1042,
    "status": "paid",
    "amount": "2500.00",
    "currency": "BDT",
    "paymentMethod": "cod",
    "paymentStatus": "paid",
    "lineItems": [ { "productId": "…", "productName": "Cotton Panjabi", "quantity": 2, "price": 1250, "total": 2500 } ],
    "customer": { "id": "…", "firstName": "Rahim", "phone": "01712345678" },
    "shippingAddress": { "id": "…", "addressLine1": "House 12, Road 5, Dhanmondi", "city": "Dhaka" }
  },
  "timestamp": "2026-10-30T12:34:56.789Z"
}

Verify the signature

X-EcomUp-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, using your signing secret as the key. Reject requests where it doesn't match.

// Node.js (Express): use express.raw() so you get the raw body
const crypto = require("crypto");
const expected = "sha256=" +
  crypto.createHmac("sha256", SIGNING_SECRET).update(rawBody).digest("hex");
const received = req.header("X-EcomUp-Signature") || "";
const ok = expected.length === received.length &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!ok) return res.status(401).send("Invalid signature");
res.sendStatus(200);
<?php
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_ECOMUP_SIGNATURE'] ?? '';
$expected  = 'sha256=' . hash_hmac('sha256', $payload, $signingSecret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}
http_response_code(200);

Related articles

Let EcomUp answer the next message.

Connect your page, add a few products and see how it replies before you switch it on.

No card needed to sign up. Or email support@ecomup.ai.