Scroll to top
Developer Documentation
Raw .md

Custom Storefront API & Webhooks

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.

REST API & Webhooks
HMAC-SHA256 Signatures
Instant Order Dispatch
Headless & Omnichannel

1. Overview & Architecture

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.

Two-Way Communication Model:
Inbound (Your Store → Ecomas): Query catalog items, synchronize inventory, and look up past orders.
Outbound (Ecomas → Your Store): When an AI agent confirms a customer purchase on social chat channels, Ecomas sends an HTTP POST event to your configured Webhook URL with full order, line item, and customer details.

2. Quick Start (5-Minute Integration)

Follow three quick steps to activate your custom storefront bridge:

  1. Step 1: Navigate to your Ecomas Dashboard → Store Configuration → Custom Storefront API.
  2. Step 2: Copy your automatically generated API Key (e.g. esk_live_...) and API Secret (e.g. ess_...).
  3. Step 3: Enter your public HTTPS Webhook URL (e.g. https://yourstore.com/api/webhooks/ecomas-order), select Order Routing Target: Custom Webhook, switch the status to Enabled, and click Save Bridge Configuration.
Single Active CMS Rule: Ecomas guarantees that only one active store CMS (Shopify, WooCommerce, Wix, or Custom API) is active per shop at any time. Enabling Custom Storefront automatically sets other integrations to standby, preventing duplicate catalog and order processing.

3. Authentication & API Headers

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();

4. Outbound Webhook: Order Created

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.

WEBHOOK Your Webhook URL (e.g. https://yourstore.com/api/webhooks/ecomas-order)

Delivered HTTP Headers:

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.

Example JSON Webhook Payload:

order.created event payload
{
  "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
      }
    ]
  }
}

Expected Webhook Response:

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:

200 OK response body
{
  "received": true,
  "external_order_id": "STORE-ORD-98412"
}

Product & Gallery Updates: product.updated & product.image.updated

Whenever 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:

product.image.updated event payload
{
  "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"
  }
}

5. Webhook Signature Verification

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 Formula:
signature = HMAC_SHA256(timestamp + "." + raw_json_body, api_secret)
Always verify the signature against the raw binary request body before JSON parsing. Reject any webhook whose timestamp is older than 300 seconds (5 minutes).
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'],
        ]);
    }
}

6. Product Catalog Endpoints

Fetch products, search catalog items, and verify inventory quantities so that your frontend and AI chat bots operate with synchronized data.

GET /api/agent/shop/{shop_id}/products

Query Parameters:

ParameterTypeDescription
query Optional Keyword search on product title, SKU, or description.
category Optional Filter by product category name.
limit Optional Default 20, maximum 100.

Example Response:

{
  "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 }
      ]
    }
  ]
}

7. Real-Time Inventory & Stock Check

Perform instant stock verifications before taking customer payment or confirming item availability.

GET /api/agent/shop/{shop_id}/products/{product_id}/stock

Query Parameters:

ParameterTypeDescription
variation_id Optional Specific variation or option SKU id.

Example Response:

{
  "product_id": 84,
  "in_stock": true,
  "stock_quantity": 45,
  "allow_backorder": false
}

8. Customer Management

Search and register customer records. When orders are created via chat, Ecomas automatically creates or links the customer by phone and email.

GET /api/agent/shop/{shop_id}/customers/search?query=+14155552671
POST /api/agent/shop/{shop_id}/customers

POST Request Body Schema:

{
  "name": "Sarah Connor",
  "phone": "+14155552671",
  "email": "sarah.connor@example.com",
  "address": "742 Evergreen Terrace, Apt 4B",
  "city": "San Francisco"
}

9. Inbound Order Management

Directly inject customer orders from external sources into Ecomas so they appear in your unified analytics and automated conversational messaging queues.

POST /api/agent/shop/{shop_id}/orders

Request Body Schema:

{
  "customer_id": 589,
  "items": [
    {
      "product_id": 84,
      "quantity": 1,
      "unit_price": 129.00
    }
  ],
  "notes": "Handle with care",
  "source_channel": "custom_storefront"
}

Response:

{
  "status": "success",
  "order_id": 1042,
  "external_order_id": null,
  "total_amount": 129.00,
  "message": "Order created successfully"
}
GET /api/agent/shop/{shop_id}/orders/{order_id}

Fetch current status, tracking details, customer address, and line items for any order ID.

10. Sandbox & Local Webhook Testing

Testing webhooks on localhost is easy using secure tunneling tools. We recommend using Cloudflare Tunnels or Ngrok:

Expose local port 3000 to public HTTPS
# 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!

11. HTTP Status & Error Codes

Ecomas returns standard HTTP response codes for all API and webhook requests:

StatusMeaningResolution
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.

12. Production Security Best Practices

  • Keep Your API Secret Server-Side: Never bundle api_secret into client-side JavaScript, single-page application bundles, or mobile app binaries.
  • Idempotent Webhook Processing: Store incoming 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.
  • Acknowledge Promptly: Return 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.
  • Rotate Credentials Safely: If a credential is compromised, use the Rotate Keys action in your Ecomas Dashboard to generate a new key pair with zero downtime.

Ready to configure your custom store?

Access your store credentials and configure your webhook URL in the Ecomas dashboard.

Open Storefront Configuration