Connect bespoke headless frontends (Next.js, Nuxt, SvelteKit), custom backends (Laravel, Django, Node.js), mobile apps, or enterprise ERPs directly to the Ecomas AI support and commerce automation engine.
The Ecomas Custom Storefront API provides a two-way synchronization bridge between your custom e-commerce infrastructure and our conversational AI engine. It allows your AI chatbots running on WhatsApp, Instagram, Facebook Messenger, and Web Chat to query live product inventory, look up customer profiles, and push closed sales into your order fulfillment pipeline in real time.
Follow three quick steps to activate your custom storefront bridge:
API Key (e.g. esk_live_...) and API Secret (e.g. ess_...).https://yourstore.com/api/webhooks/ecomas-order), select Order Routing Target: Custom Webhook, switch the status to Enabled, and click Save Bridge Configuration.
All requests to Ecomas API endpoints must be served over secure HTTPS and authenticated using your store's API key. You can supply the token either via the standard Authorization: Bearer header or via the custom X-Store-Api-Key header.
| Header | Type | Description |
|---|---|---|
Authorization |
Required | Bearer esk_live_your_store_api_key |
X-Store-Api-Key |
Alternative | Directly pass your API key without the Bearer prefix. |
Content-Type |
Required | application/json for all POST and PUT request bodies. |
Accept |
Required | application/json |
curl -X GET "https://ecomasai.com/api/agent/shop/12/products" \
-H "Authorization: Bearer esk_live_a1b2c3d4e5f6g7h8i9j0" \
-H "Accept: application/json"
const response = await fetch('https://ecomasai.com/api/agent/shop/12/products', {
method: 'GET',
headers: {
'Authorization': 'Bearer esk_live_a1b2c3d4e5f6g7h8i9j0',
'Accept': 'application/json'
}
});
const products = await response.json();
console.log(products);
import requests
url = "https://ecomasai.com/api/agent/shop/12/products"
headers = {
"Authorization": "Bearer esk_live_a1b2c3d4e5f6g7h8i9j0",
"Accept": "application/json"
}
response = requests.get(url, headers=headers, timeout=10)
print(response.json())
use Illuminate\Support\Facades\Http;
$response = Http::withToken('esk_live_a1b2c3d4e5f6g7h8i9j0')
->acceptJson()
->get('https://ecomasai.com/api/agent/shop/12/products');
$products = $response->json();
Whenever an AI Chatbot confirms an order with a customer on WhatsApp, Instagram DM, Facebook Messenger, or Web Chat, Ecomas immediately delivers an HTTP POST request to your configured Webhook URL.
| Header | Example Value | Description |
|---|---|---|
Content-Type |
application/json |
JSON payload format. |
User-Agent |
Ecomas-Webhook/1.0 |
Identifies Ecomas webhook client. |
X-Ecomas-Event |
order.created |
Event name indicator. |
X-Ecomas-Timestamp |
1791012345 |
Epoch timestamp (in seconds) of when the payload was signed. |
X-Ecomas-Signature |
e3b0c44298fc1c149afbf4c8... |
Hex-encoded HMAC-SHA256 signature calculated with your api_secret. |
X-Ecomas-Shop-Id |
12 |
Your Ecomas Shop ID. |
{
"event": "order.created",
"timestamp": 1791012345,
"shop_id": 12,
"shop_name": "Artisan Craft Studio",
"data": {
"order": {
"id": 1042,
"status": "pending",
"total_amount": 149.00,
"currency": "USD",
"currency_symbol": "$",
"notes": "Please package with care for a birthday gift.",
"source_channel": "whatsapp",
"created_at": "2026-10-03T07:30:00+00:00"
},
"customer": {
"id": 589,
"name": "Sarah Connor",
"phone": "+14155552671",
"email": "sarah.connor@example.com",
"address": "742 Evergreen Terrace, Apt 4B",
"city": "San Francisco"
},
"items": [
{
"product_id": 84,
"title": "Minimalist Ceramic Watch",
"sku": "WATCH-SL-01",
"quantity": 1,
"unit_price": 129.00,
"total_price": 129.00,
"variation_id": null
},
{
"product_id": 92,
"title": "Gift Wrap Ribbon & Card",
"sku": "GIFT-WRAP",
"quantity": 1,
"unit_price": 20.00,
"total_price": 20.00,
"variation_id": null
}
]
}
}
Your server must respond with an HTTP status code of 200 OK or 201 Created within 10 seconds. You may optionally return your internal order ID in the response JSON, and Ecomas will record it as the external_id for traceability:
{
"received": true,
"external_order_id": "STORE-ORD-98412"
}
product.updated & product.image.updatedWhenever product information, stock, pricing, or product images (including AI Studio re-imagined images) are updated in Ecomas, your store webhook automatically receives an update payload containing the latest gallery URLs and primary image:
{
"event": "product.image.updated",
"timestamp": 1791255000,
"shop_id": 12,
"shop_name": "Artisan Craft Studio",
"data": {
"product_id": 84,
"external_id": "WATCH-SL-01",
"sku": "WATCH-SL-01",
"title": "Minimalist Ceramic Watch",
"status": "active",
"price": 149.00,
"compare_price": null,
"quantity": 45,
"action": "replace",
"image_url": "https://ecomasai.com/storage/product-images/uuid.png",
"replace_index": 0,
"images": [
"https://ecomasai.com/storage/product-images/uuid.png",
"https://ecomasai.com/storage/product-images/gallery-2.png"
],
"images_detail": [
{
"id": 369,
"url": "https://ecomasai.com/storage/product-images/uuid.png",
"sort_order": 0,
"is_primary": true
}
],
"updated_at": "2026-10-06T03:00:00+00:00"
}
}
To guarantee authenticity and prevent man-in-the-middle or replay attacks, Ecomas signs every webhook payload using HMAC-SHA256 with your store's api_secret.
signature = HMAC_SHA256(timestamp + "." + raw_json_body, api_secret)const express = require('express');
const crypto = require('crypto');
const app = express();
const API_SECRET = process.env.ECOMAS_API_SECRET; // e.g. ess_...
// Use express.raw() to preserve the raw buffer for HMAC verification
app.post('/api/webhooks/ecomas-order', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.headers['x-ecomas-timestamp'];
const signature = req.headers['x-ecomas-signature'];
if (!timestamp || !signature) {
return res.status(401).json({ error: 'Missing webhook signature headers' });
}
// Guard against replay attacks: reject timestamps older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
return res.status(401).json({ error: 'Timestamp expired' });
}
// Calculate expected signature
const rawBody = req.body.toString('utf8');
const expectedSignature = crypto
.createHmac('sha256', API_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// Constant-time comparison prevents timing attacks
const isValid = crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
if (!isValid) {
return res.status(403).json({ error: 'Invalid HMAC signature' });
}
const payload = JSON.parse(rawBody);
const order = payload.data.order;
console.log(`Received verified order #${order.id} for $${order.total_amount}`);
// Ingest order into your custom database or fulfillment queue...
return res.status(200).json({ received: true, external_order_id: `EXT-${order.id}` });
});
// app/api/webhooks/ecomas-order/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
const API_SECRET = process.env.ECOMAS_API_SECRET!;
export async function POST(req: NextRequest) {
const timestamp = req.headers.get('x-ecomas-timestamp');
const signature = req.headers.get('x-ecomas-signature');
if (!timestamp || !signature) {
return NextResponse.json({ error: 'Missing signature headers' }, { status: 401 });
}
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
return NextResponse.json({ error: 'Timestamp expired' }, { status: 401 });
}
const rawBody = await req.text();
const expectedSignature = crypto
.createHmac('sha256', API_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expectedSignature, 'hex'))) {
return NextResponse.json({ error: 'Invalid HMAC signature' }, { status: 403 });
}
const payload = JSON.parse(rawBody);
const order = payload.data.order;
// Process order in your database...
return NextResponse.json({ received: true, external_order_id: `NEXT-${order.id}` });
}
from fastapi import FastAPI, Request, HTTPException, Header
import hmac
import hashlib
import time
import os
import json
app = FastAPI()
API_SECRET = os.getenv("ECOMAS_API_SECRET", "ess_...")
@app.post("/api/webhooks/ecomas-order")
async def handle_ecomas_webhook(
request: Request,
x_ecomas_timestamp: str = Header(None),
x_ecomas_signature: str = Header(None)
):
if not x_ecomas_timestamp or not x_ecomas_signature:
raise HTTPException(status_code=401, detail="Missing signature headers")
# Reject if timestamp older than 300 seconds
if abs(time.time() - int(x_ecomas_timestamp)) > 300:
raise HTTPException(status_code=401, detail="Timestamp expired")
raw_body = await request.body()
signed_payload = f"{x_ecomas_timestamp}.{raw_body.decode('utf-8')}".encode('utf-8')
expected = hmac.new(API_SECRET.encode('utf-8'), signed_payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, x_ecomas_signature):
raise HTTPException(status_code=403, detail="Invalid HMAC signature")
payload = json.loads(raw_body)
order = payload["data"]["order"]
print(f"Verified order #{order['id']} from channel {order['source_channel']}")
return {"received": True, "external_order_id": f"PY-{order['id']}"}
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class EcomasWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
$timestamp = $request->header('X-Ecomas-Timestamp');
$signature = $request->header('X-Ecomas-Signature');
$apiSecret = config('services.ecomas.api_secret');
if (! $timestamp || ! $signature) {
return response()->json(['error' => 'Missing signature headers'], 401);
}
// Check freshness (max 5 minutes)
if (abs(now()->timestamp - (int) $timestamp) > 300) {
return response()->json(['error' => 'Timestamp expired'], 401);
}
$rawBody = $request->getContent();
$expectedSignature = hash_hmac('sha256', "{$timestamp}.{$rawBody}", $apiSecret);
if (! hash_equals($expectedSignature, $signature)) {
return response()->json(['error' => 'Invalid HMAC signature'], 403);
}
$order = $request->input('data.order');
// Record order in your store database...
return response()->json([
'received' => true,
'external_order_id' => 'LARAVEL-' . $order['id'],
]);
}
}
Fetch products, search catalog items, and verify inventory quantities so that your frontend and AI chat bots operate with synchronized data.
| Parameter | Type | Description |
|---|---|---|
query |
Optional | Keyword search on product title, SKU, or description. |
category |
Optional | Filter by product category name. |
limit |
Optional | Default 20, maximum 100. |
{
"products": [
{
"id": 84,
"title": "Minimalist Ceramic Watch",
"sku": "WATCH-SL-01",
"price": 129.00,
"stock": 45,
"is_active": true,
"image_url": "https://ecomasai.com/storage/products/watch-1.png",
"description": "Premium sapphire crystal with Swiss quartz movement.",
"variations": [
{ "id": 12, "name": "Color", "value": "Silver", "stock": 25, "price": 129.00 },
{ "id": 13, "name": "Color", "value": "Rose Gold", "stock": 20, "price": 139.00 }
]
}
]
}
Perform instant stock verifications before taking customer payment or confirming item availability.
| Parameter | Type | Description |
|---|---|---|
variation_id |
Optional | Specific variation or option SKU id. |
{
"product_id": 84,
"in_stock": true,
"stock_quantity": 45,
"allow_backorder": false
}
Search and register customer records. When orders are created via chat, Ecomas automatically creates or links the customer by phone and email.
{
"name": "Sarah Connor",
"phone": "+14155552671",
"email": "sarah.connor@example.com",
"address": "742 Evergreen Terrace, Apt 4B",
"city": "San Francisco"
}
Directly inject customer orders from external sources into Ecomas so they appear in your unified analytics and automated conversational messaging queues.
{
"customer_id": 589,
"items": [
{
"product_id": 84,
"quantity": 1,
"unit_price": 129.00
}
],
"notes": "Handle with care",
"source_channel": "custom_storefront"
}
{
"status": "success",
"order_id": 1042,
"external_order_id": null,
"total_amount": 129.00,
"message": "Order created successfully"
}
Fetch current status, tracking details, customer address, and line items for any order ID.
Testing webhooks on localhost is easy using secure tunneling tools. We recommend using Cloudflare Tunnels or Ngrok:
# Using Cloudflare Tunnel (Free, no account needed)
npx cloudflared tunnel --url http://localhost:3000
# Using Ngrok
ngrok http 3000
Copy the generated HTTPS URL (e.g. https://subdomain.trycloudflare.com/api/webhooks/ecomas-order) into your Ecomas Custom Storefront dashboard settings. Now you can place a test order via your connected AI chatbot, and inspect the incoming payload in your local console in real time!
Ecomas returns standard HTTP response codes for all API and webhook requests:
| Status | Meaning | Resolution |
|---|---|---|
200 OK |
Success | Request completed successfully. |
400 Bad Request |
Invalid Request | Missing required fields, invalid quantity, or malformed JSON. |
401 Unauthorized |
Authentication Failed | Missing or invalid Authorization bearer token or expired webhook timestamp. |
403 Forbidden |
Forbidden / Signature Mismatch | The computed HMAC signature does not match X-Ecomas-Signature. |
404 Not Found |
Resource Not Found | Product, customer, or order ID does not exist in this shop scope. |
422 Unprocessable |
Validation Error | Check error array for specific field constraints. |
500 Server Error |
Internal Error | Temporary server error. Requests will automatically be retried. |
api_secret into client-side JavaScript, single-page application bundles, or mobile app binaries.data.order.id in your database. If a network retry delivers the same order ID again, acknowledge with 200 OK immediately without duplicating inventory deductions.200 OK to webhook deliveries within 5 seconds. If you have slow downstream jobs (like generating custom PDFs or calling third-party shipping APIs), push the event to a background queue (such as Redis, BullMQ, or SQS) before processing.Access your store credentials and configure your webhook URL in the Ecomas dashboard.