API Reference v1.0

Generic API Connector

Connect any e-commerce platform to HiWelink. Implement REST endpoints, secure with authentication, and sync products & orders for an AI-powered shopping assistant.

Auth Methods
API Key · Bearer · Basic
Response Format
JSON
Sync Methods
Polling + Webhooks
Retry Logic
3× with backoff

Quick Start

The Generic API connector lets you integrate any REST-capable e-commerce platform with HiWelink. Follow these four steps to get started.

01

Expose REST Endpoints

Add /health, /api/products, and /api/orders endpoints to your store backend. Paths are fully customizable.

02

Generate API Credentials

Create a dedicated API key, bearer token, or basic-auth user specifically for HiWelink. Scope it read-only.

03

Connect in HiWelink

In the Commerce Dashboard, add a Generic API store. Enter your base URL, auth credentials, and endpoint paths.

04

Sync & Go Live

Trigger an initial product and order sync. Optionally configure webhooks for real-time updates. Your chatbot is ready.

Full Integration Flow

End-to-end data path from setup through live chatbot usage.

Phase 1 Setup — Your E-Commerce Backend

  1. Implement the three required endpoints: /health, /api/products, /api/orders.
  2. Return JSON responses following the schema described in the Response Format section.
  3. Add authentication middleware that validates the credential you will share with HiWelink.
  4. Ensure your server is publicly accessible via HTTPS (TLS required for production).

Phase 2 Connection — HiWelink Dashboard

  1. Navigate to Commerce → Add Store → Generic API.
  2. Enter your store's base URL (e.g. https://api.yourstore.com).
  3. Select the authentication method and paste your credentials.
  4. Optionally override endpoint paths and field mappings.
  5. Click Connect — HiWelink calls /health to verify, then activates the store.

Phase 3 Initial Sync — Data Import

  1. HiWelink automatically dispatches background jobs to fetch all products and orders.
  2. Products are fetched page-by-page (50 per page, 200 ms between pages).
  3. Each product is mapped to HiWelink's internal schema using field mappings.
  4. Once all products are synced, AI embeddings are generated for semantic search.
  5. Progress is visible in the Commerce Dashboard under Sync Runs.

Phase 4 Ongoing Sync — Keeping Data Fresh

  1. Manual sync: trigger from the dashboard at any time.
  2. Scheduled sync: HiWelink polls your endpoints on a configurable interval.
  3. Real-time sync: configure webhooks in your store to push changes instantly.

Phase 5 Usage — AI Chatbot in Production

  1. Create a chatbot and link it to your connected commerce store.
  2. Embed the chatbot widget on your storefront using the generated script tag.
  3. The chatbot uses AI embeddings to answer product questions and assist shoppers.

Required Endpoints

Your API must expose the following three endpoints. All endpoints must:

  • Return HTTP 200 on success.
  • Respond with Content-Type: application/json.
  • Validate the authentication credential on every request.
  • Be served over HTTPS in production.
GET /health Default path (customizable)

Health check endpoint. HiWelink calls this during connection and reconnection to verify your API is reachable. Must return HTTP 200.

// Minimal response
{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00Z"
}
Note: HiWelink checks that the HTTP status code is 200. The response body can be any valid JSON.
GET /api/products Default path (customizable)

Returns a paginated list of products. HiWelink iterates all pages until exhausted.

Query parameters sent by HiWelink: ?page=1&per_page=50

// Standard paginated response (recommended)
{
  "data": [
    {
      "id": "prod_123",
      "name": "Wireless Headphones",
      "description": "Premium noise-cancelling wireless headphones with 40-hour battery life.",
      "short_description": "40hr battery, noise-cancelling, Bluetooth 5.2",
      "sku": "WH-1000XM5",
      "price": 349.99,
      "regular_price": 399.99,
      "sale_price": 349.99,
      "stock_status": "in_stock",
      "stock_quantity": 42,
      "categories": ["Electronics", "Audio"],
      "tags": ["wireless", "bluetooth", "noise-cancelling"],
      "images": [
        "https://yourstore.com/images/wh-1000xm5-main.jpg",
        "https://yourstore.com/images/wh-1000xm5-side.jpg"
      ],
      "attributes": {
        "color": "Black",
        "connectivity": "Bluetooth 5.2",
        "battery": "40 hours"
      }
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 50,
    "total": 250,
    "last_page": 5
  }
}
Required fields per product: id, name. HiWelink will skip any product missing both fields. All other fields are optional but recommended for richer chatbot answers.
GET /api/orders Default path (customizable)

Returns a paginated list of orders. Used for order-status queries and customer service in the chatbot.

{
  "data": [
    {
      "id": "order_456",
      "order_number": "ORD-2024-0456",
      "status": "completed",
      "total": 699.98,
      "subtotal": 649.98,
      "tax": 32.50,
      "shipping": 17.50,
      "currency": "USD",
      "customer_email": "alice@example.com",
      "customer_name": "Alice Johnson",
      "items": [
        {
          "product_id": "prod_123",
          "name": "Wireless Headphones",
          "quantity": 2,
          "price": 349.99
        }
      ],
      "shipping_address": {
        "street": "123 Main St",
        "city": "New York",
        "state": "NY",
        "zip": "10001",
        "country": "US"
      },
      "billing_address": {
        "street": "123 Main St",
        "city": "New York",
        "state": "NY",
        "zip": "10001",
        "country": "US"
      },
      "payment_method": "credit_card",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 50,
    "total": 1200,
    "last_page": 24
  }
}
Required field: id. All other fields are optional. Provide as much detail as possible to enable order-status queries from the chatbot.

Authentication Methods

HiWelink supports three authentication methods. Choose the one your platform supports and configure it in your API middleware. HiWelink sends the credential on every request.

METHOD 1

API Key

Recommended

HiWelink sends the API key as a custom HTTP header on every request. This is the simplest and most widely supported method.

GET /api/products HTTP/1.1
Host: api.yourstore.com
X-API-Key: sk_live_4f8d2a1b9c3e7f6a0b2d4e8c1a3f5b7d
Accept: application/json
How to validate in your backend:

Read the X-API-Key header value and compare it against a stored secret. Reject with HTTP 401 if it does not match.

METHOD 2

Bearer Token (JWT or Static)

HiWelink sends the token in the standard Authorization header. Supports both static tokens and JWTs.

GET /api/products HTTP/1.1
Host: api.yourstore.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json
Static token:

A long, randomly generated string stored securely. Validate by direct comparison.

JWT token:

A signed JSON Web Token. Validate the signature and expiry claim using your secret key.

METHOD 3

HTTP Basic Auth

HiWelink encodes username:password in Base64 and sends it in the Authorization header.

GET /api/products HTTP/1.1
Host: api.yourstore.com
Authorization: Basic Ym90bWVyemU6c3VwZXJzZWNyZXQ=
Accept: application/json

// Decoded value: base64("hiwelink:supersecret")
Security note: Basic Auth credentials are only protected by HTTPS. Always use HTTPS (TLS) in production. Never expose these endpoints over plain HTTP.

API Credentials

You create a credential on your own store (an API key, token, or username/password) and paste it into HiWelink when connecting. HiWelink takes it from there — storing it securely and attaching it to every request it makes to your API.

How it works

  1. 1

    Generate a credential in your store

    Use your platform's built-in API key manager, admin panel, or a simple config value. The credential only needs to work for read-only access to your products and orders endpoints.

  2. 2

    Copy and paste it into HiWelink

    When adding your store in the Commerce Dashboard, select the auth method that matches what your backend expects, then paste the value into the corresponding field.

  3. 3

    HiWelink handles the rest

    HiWelink securely stores the credential and automatically attaches it to every sync request it sends to your API. You never need to manage it again unless you rotate it.

Need a token? Generate one here

Creates a secure random token in your browser. Works for all three auth methods — API Key, Bearer Token, or Basic Auth password. Copy it, add it to your store config, then paste the same value into HiWelink when connecting.

What to provide per auth method

API KEY

HiWelink sends the token as X-API-Key: <token>. Your store reads that header and compares the value against what you configured.

Request HiWelink sends:
X-API-Key: sk_live_4f8d2a1b9c3e7f6a...

You can use the generated token above as this value.

BEARER TOKEN

HiWelink sends the same token as Authorization: Bearer <token>. The token value is identical — only the header format differs from API Key.

Request HiWelink sends:
Authorization: Bearer sk_live_4f8d2a1b9c3e7f6a...

You can use the generated token above as this value too — just validate the Authorization header instead of X-API-Key.

BASIC AUTH

HiWelink encodes username:password in Base64 and sends it as Authorization: Basic <encoded>. You can use the generated token as the password.

What you enter in HiWelink:
Username: hiwelink
Password: sk_live_4f8d2a1b9c3e7f6a... ← use generated token
Request HiWelink sends:
Authorization: Basic Ym90bWVyemU6c2tfbGl2ZV8...

How HiWelink handles your credential

What HiWelink stores

  • • The auth method (api_key / bearer_token / basic_auth)
  • • The credential value(s) — encrypted at rest
  • • Credential fields are never exposed in API responses

Credential lifecycle

  • • Transmitted only over HTTPS
  • • Never written to logs
  • • Automatically deleted when you disconnect the store
  • • To rotate: disconnect, update the key in your store, reconnect

Store-Side Setup

Once you have your credentials, you need to configure three things inside your e-commerce store:

  1. Store the three HIWELINK values in your environment config.
  2. Protect your API endpoints by validating the API key HiWelink sends.
  3. Push product/order changes to HiWelink via webhooks.

The three values you need

Variable What it is Where to get it
HIWELINK_API_KEY The token HiWelink sends on every request to your store. Your middleware validates it. Generate it on this page ↑ and paste the same value into HiWelink when connecting.
HIWELINK_WEBHOOK_URL The base URL of your HiWelink installation. Your store POSTs events here. The root URL you use to access HiWelink, e.g. https://app.hiwelink.com
HIWELINK_STORE_TOKEN Identifies your store in webhook URLs. Included in every webhook path. Commerce Dashboard → select your store → copy the Store Token shown on the detail page.
Step 1 — .env
HIWELINK_API_KEY=sk_live_87db8f090a2544ac2bd658ebd6f042db...
HIWELINK_WEBHOOK_URL=https://app.hiwelink.com
HIWELINK_STORE_TOKEN=88bedac0-a8da-4700-8d07-5dbbf4fdfd6d
Step 2 — config/hiwelink.php
<?php
// config/hiwelink.php
return [
    'api_key'      => env('HIWELINK_API_KEY', ''),
    'webhook_url'  => env('HIWELINK_WEBHOOK_URL', ''),
    'store_token'  => env('HIWELINK_STORE_TOKEN', ''),
];
Step 3 — Middleware to protect your endpoints
<?php
// app/Http/Middleware/HiWelinkAuth.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class HiWelinkAuth
{
    public function handle(Request $request, Closure $next)
    {
        $expected = config('hiwelink.api_key');
        $incoming = $request->header('X-API-Key');

        if (empty($expected) || $incoming !== $expected) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $next($request);
    }
}

// routes/api.php
Route::middleware(\App\Http\Middleware\HiWelinkAuth::class)->group(function () {
    Route::get('/health',       fn() => response()->json(['status' => 'ok']));
    Route::get('/api/products', [ProductController::class, 'index']);
    Route::get('/api/orders',   [OrderController::class, 'index']);
});
Step 4 — Webhook service to push changes to HiWelink
<?php
// app/Services/HiWelinkWebhookService.php
namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class HiWelinkWebhookService
{
    public static function sendProductEvent(string $event, $product): void
    {
        $base  = config('hiwelink.webhook_url');
        $token = config('hiwelink.store_token');

        if (empty($base) || empty($token)) {
            return; // not configured yet — skip silently
        }

        $url = rtrim($base, '/') . '/api/generic-api/webhooks/' . $token . '/products';

        Http::post($url, [
            'event' => $event,   // product.created | product.updated | product.deleted
            'data'  => [
                'id'           => (string) $product->id,
                'name'         => $product->name,
                'price'        => (float) ($product->sale_price ?? $product->price ?? 0),
                'stock_status' => $product->stock > 0 ? 'in_stock' : 'out_of_stock',
            ],
        ]);
    }

    public static function sendOrderEvent(string $event, $order): void
    {
        $base  = config('hiwelink.webhook_url');
        $token = config('hiwelink.store_token');

        if (empty($base) || empty($token)) {
            return;
        }

        $url = rtrim($base, '/') . '/api/generic-api/webhooks/' . $token . '/orders';

        Http::post($url, [
            'event' => $event,   // order.created | order.updated | order.deleted
            'data'  => [
                'id'             => (string) $order->id,
                'order_number'   => $order->order_number,
                'status'         => $order->status,
                'total'          => (float) $order->total,
                'customer_email' => $order->customer_email,
            ],
        ]);
    }
}
Step 5 — Call it from your Observers or Events
<?php
// app/Observers/ProductObserver.php
use App\Services\HiWelinkWebhookService;

class ProductObserver
{
    public function created(Product $product): void
    {
        HiWelinkWebhookService::sendProductEvent('product.created', $product);
    }

    public function updated(Product $product): void
    {
        HiWelinkWebhookService::sendProductEvent('product.updated', $product);
    }

    public function deleted(Product $product): void
    {
        HiWelinkWebhookService::sendProductEvent('product.deleted', $product);
    }
}

// app/Providers/AppServiceProvider.php
Product::observe(ProductObserver::class);
Order::observe(OrderObserver::class);
Step 1 — .env
HIWELINK_API_KEY=sk_live_87db8f090a2544ac2bd658ebd6f042db...
HIWELINK_WEBHOOK_URL=https://app.hiwelink.com
HIWELINK_STORE_TOKEN=88bedac0-a8da-4700-8d07-5dbbf4fdfd6d
Step 2 — Middleware to protect your endpoints
// middleware/hiwelinkAuth.js
function hiwelinkAuth(req, res, next) {
    const incoming = req.headers['x-api-key'];
    const expected = process.env.HIWELINK_API_KEY;

    if (!expected || incoming !== expected) {
        return res.status(401).json({ error: 'Unauthorized' });
    }
    next();
}

// Apply to your store endpoints
app.use(['/health', '/api/products', '/api/orders'], hiwelinkAuth);
Step 3 — Webhook service
// services/hiwelinkWebhook.js
const axios = require('axios');

const base  = process.env.HIWELINK_WEBHOOK_URL;
const token = process.env.HIWELINK_STORE_TOKEN;

async function sendProductEvent(event, product) {
    if (!base || !token) return;

    const url = `${base.replace(/\/$/, '')}/api/generic-api/webhooks/${token}/products`;

    await axios.post(url, {
        event,
        data: {
            id:           String(product.id),
            name:         product.name,
            price:        parseFloat(product.salePrice ?? product.price ?? 0),
            stock_status: product.stock > 0 ? 'in_stock' : 'out_of_stock',
        },
    }).catch(err => console.error('HiWelink webhook failed:', err.message));
}

async function sendOrderEvent(event, order) {
    if (!base || !token) return;

    const url = `${base.replace(/\/$/, '')}/api/generic-api/webhooks/${token}/orders`;

    await axios.post(url, {
        event,
        data: {
            id:             String(order.id),
            order_number:   order.orderNumber,
            status:         order.status,
            total:          parseFloat(order.total),
            customer_email: order.customerEmail,
        },
    }).catch(err => console.error('HiWelink webhook failed:', err.message));
}

module.exports = { sendProductEvent, sendOrderEvent };

// Usage in your route/controller after saving:
// await sendProductEvent('product.updated', product);
// await sendOrderEvent('order.created', order);
Step 1 — .env
HIWELINK_API_KEY=sk_live_87db8f090a2544ac2bd658ebd6f042db...
HIWELINK_WEBHOOK_URL=https://app.hiwelink.com
HIWELINK_STORE_TOKEN=88bedac0-a8da-4700-8d07-5dbbf4fdfd6d
Step 2 — Middleware / dependency to protect endpoints
# FastAPI
import os
from fastapi import Header, HTTPException, Depends

def hiwelink_auth(x_api_key: str = Header(...)):
    expected = os.getenv("HIWELINK_API_KEY", "")
    if not expected or x_api_key != expected:
        raise HTTPException(status_code=401, detail="Unauthorized")

# Apply to your endpoints
@app.get("/health", dependencies=[Depends(hiwelink_auth)])
def health():
    return {"status": "ok"}

@app.get("/api/products", dependencies=[Depends(hiwelink_auth)])
def list_products(): ...
Step 3 — Webhook service
# services/hiwelink_webhook.py
import os
import httpx

BASE_URL    = os.getenv("HIWELINK_WEBHOOK_URL", "")
STORE_TOKEN = os.getenv("HIWELINK_STORE_TOKEN", "")

def send_product_event(event: str, product) -> None:
    if not BASE_URL or not STORE_TOKEN:
        return

    url = f"{BASE_URL.rstrip('/')}/api/generic-api/webhooks/{STORE_TOKEN}/products"

    with httpx.Client() as client:
        client.post(url, json={
            "event": event,  # product.created | product.updated | product.deleted
            "data": {
                "id":           str(product.id),
                "name":         product.name,
                "price":        float(product.sale_price or product.price or 0),
                "stock_status": "in_stock" if product.stock > 0 else "out_of_stock",
            },
        })

def send_order_event(event: str, order) -> None:
    if not BASE_URL or not STORE_TOKEN:
        return

    url = f"{BASE_URL.rstrip('/')}/api/generic-api/webhooks/{STORE_TOKEN}/orders"

    with httpx.Client() as client:
        client.post(url, json={
            "event": event,  # order.created | order.updated | order.deleted
            "data": {
                "id":             str(order.id),
                "order_number":   order.order_number,
                "status":         order.status,
                "total":          float(order.total),
                "customer_email": order.customer_email,
            },
        })

# Usage — call after saving/updating:
# send_product_event("product.updated", product)
# send_order_event("order.created", order)

Where to find HIWELINK_STORE_TOKEN

Connect your store first (steps in the "Connect to HiWelink" section below), then go to Commerce Dashboard → click your store name → the Store Token is shown on the detail page. Copy it into your .env file.

Response Format Reference

Product Fields

Product detail URL — strongly recommended:

To make chatbot "View Product" links open the exact product page, your API should return either a full permalink (absolute URL) OR a slug / handle. If a slug is provided, HiWelink constructs the URL as {store_base_url}/product/{slug}. If neither is present, the chatbot will fall back to the store homepage and a warning will be logged.

Valid — full permalink
{
  "id": "42",
  "name": "Blue Hoodie",
  "permalink": "https://store.com/product/blue-hoodie"
}
Valid — slug only
{
  "id": "42",
  "name": "Blue Hoodie",
  "slug": "blue-hoodie"
}
Invalid — falls back to homepage
{
  "id": "42",
  "name": "Blue Hoodie"
}
Field Type Required Accepted Aliases Notes
id string / int Required product_id, sku Unique external ID
name string Required title, product_name Product display name
price float Optional selling_price, current_price Current selling price
regular_price float Optional original_price, list_price Pre-discount price
sale_price float Optional discount_price Discounted price
sku string Optional product_sku, code Stock keeping unit
description string Optional full_description, body HTML or plain text
short_description string Optional summary, excerpt Brief summary
stock_status string Optional availability in_stock, out_of_stock, available…
stock_quantity int Optional stock, inventory, quantity Units available
categories array Optional category, product_categories Array of strings or CSV
tags array Optional product_tags, labels Array of strings or CSV
images array Optional photos, gallery, image_urls Array of absolute image URLs
attributes object / array Optional variations, options Key-value pairs for size/colour etc.
permalink string (URL) Recommended product_url, url, link, product_link, html_url, web_url, href, page_url, detail_url Absolute URL to the product detail page. Required for accurate "View Product" links unless a slug is supplied.
slug string Recommended handle, product_slug, permalink_slug, url_key, url_slug, seo_slug URL-safe identifier. Used to build {store_base_url}/product/{slug} when no permalink is provided.

Order Fields

Field Type Required Accepted Aliases
id string / int Required order_id, order_number
order_number string Optional order_no, number
status string Optional order_status, state
total float Optional total_amount, grand_total, order_total
customer_email string Optional email, billing_email
customer_name string Optional customer, buyer_name
items array Optional line_items, products, order_items
currency string Optional currency_code
created_at ISO 8601 / timestamp Optional date, order_date, created_date

Custom Field Mapping

If your API uses different field names, configure custom field mappings when connecting your store. HiWelink will use your mapping instead of the built-in aliases.

Example scenario:

Your API returns products with the field "product_title" instead of "name", and "retail_price" instead of "price". Configure field_mappings to tell HiWelink where to find each value.

// Field mappings JSON — keys are HiWelink fields, values are your API field names
{
  "name":          "product_title",
  "price":         "retail_price",
  "description":   "long_description",
  "sku":           "item_code",
  "stock_status":  "availability_status",
  "stock_quantity":"qty_on_hand",
  "categories":    "dept_names",
  "images":        "photo_urls",
  "permalink":     "product_page_url",
  "slug":          "url_key"
}

Accepted stock_status values

  • in_stock, instock, available, true, 1
  • out_of_stock, outofstock, unavailable, false, 0
  • Any other value is treated as in_stock.

Accepted price formats

  • 349.99 — number
  • "349.99" — numeric string
  • "$349.99" — currency prefix stripped
  • "USD 349.99" — currency prefix stripped

Code Examples

Full working implementations for popular backend frameworks. Each example includes the health endpoint, products, orders, and API key authentication middleware.

PHP Laravel (Recommended)


use App\Http\Middleware\ValidateBotMergeApiKey;
use Illuminate\Http\Request;

Route::middleware([ValidateBotMergeApiKey::class])->group(function () {

    Route::get('/health', fn() => response()->json([
        'status'    => 'ok',
        'timestamp' => now()->toIso8601String(),
    ]));

    Route::get('/api/products', function (Request $request) {
        $perPage = min((int) $request->get('per_page', 50), 100);

        $products = \App\Models\Product::query()
            ->where('status', 'active')
            ->paginate($perPage, ['*'], 'page', $request->get('page', 1));

        return response()->json([
            'data' => $products->map(fn ($p) => [
                'id'                => (string) $p->id,
                'name'              => $p->name,
                'description'       => $p->description,
                'short_description' => $p->short_description,
                'sku'               => $p->sku,
                'price'             => (float) $p->price,
                'regular_price'     => (float) $p->compare_price,
                'sale_price'        => $p->sale_price ? (float) $p->sale_price : null,
                'stock_status'      => $p->stock > 0 ? 'in_stock' : 'out_of_stock',
                'stock_quantity'    => (int) $p->stock,
                'categories'        => $p->categories->pluck('name')->toArray(),
                'tags'              => $p->tags->pluck('name')->toArray(),
                'images'            => $p->images->pluck('url')->toArray(),
                'attributes'        => $p->attributes ?? [],
            ]),
            'meta' => [
                'current_page' => $products->currentPage(),
                'per_page'     => $products->perPage(),
                'total'        => $products->total(),
                'last_page'    => $products->lastPage(),
            ],
        ]);
    });

    Route::get('/api/orders', function (Request $request) {
        $perPage = min((int) $request->get('per_page', 50), 100);

        $orders = \App\Models\Order::query()
            ->with(['items', 'shippingAddress', 'billingAddress'])
            ->paginate($perPage, ['*'], 'page', $request->get('page', 1));

        return response()->json([
            'data' => $orders->map(fn ($o) => [
                'id'               => (string) $o->id,
                'order_number'     => $o->order_number,
                'status'           => $o->status,
                'total'            => (float) $o->total,
                'currency'         => $o->currency ?? 'USD',
                'customer_email'   => $o->customer_email,
                'customer_name'    => $o->customer_name,
                'items'            => $o->items->map(fn ($i) => [
                    'product_id' => (string) $i->product_id,
                    'name'       => $i->name,
                    'quantity'   => (int) $i->quantity,
                    'price'      => (float) $i->price,
                ])->toArray(),
                'shipping_address' => $o->shippingAddress?->toArray(),
                'billing_address'  => $o->billingAddress?->toArray(),
                'created_at'       => $o->created_at->toIso8601String(),
            ]),
            'meta' => [
                'current_page' => $orders->currentPage(),
                'per_page'     => $orders->perPage(),
                'total'        => $orders->total(),
                'last_page'    => $orders->lastPage(),
            ],
        ]);
    });
});

JS Node.js / Express

const express = require('express');
const app     = express();

//  Middleware 
function apiKeyAuth(req, res, next) {
    const key = req.headers['x-api-key'];
    if (!key || key !== process.env.HIWELINK_API_KEY) {
        return res.status(401).json({ error: 'Invalid or missing API key' });
    }
    next();
}
app.use('/health', apiKeyAuth);
app.use('/api',    apiKeyAuth);

//  Health 
app.get('/health', (req, res) => {
    res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

//  Products ─
app.get('/api/products', async (req, res) => {
    const page    = parseInt(req.query.page    || '1');
    const perPage = Math.min(parseInt(req.query.per_page || '50'), 100);
    const offset  = (page - 1) * perPage;

    const [products, total] = await Promise.all([
        Product.findAll({ limit: perPage, offset, where: { status: 'active' } }),
        Product.count({ where: { status: 'active' } }),
    ]);

    res.json({
        data: products.map(p => ({
            id:             String(p.id),
            name:           p.name,
            description:    p.description,
            sku:            p.sku,
            price:          parseFloat(p.price),
            stock_status:   p.stock > 0 ? 'in_stock' : 'out_of_stock',
            stock_quantity: parseInt(p.stock),
            categories:     p.categories || [],
            images:         p.images     || [],
        })),
        meta: {
            current_page: page,
            per_page:     perPage,
            total,
            last_page:    Math.ceil(total / perPage),
        },
    });
});

//  Orders 
app.get('/api/orders', async (req, res) => {
    const page    = parseInt(req.query.page    || '1');
    const perPage = Math.min(parseInt(req.query.per_page || '50'), 100);
    const offset  = (page - 1) * perPage;

    const [orders, total] = await Promise.all([
        Order.findAll({ limit: perPage, offset, include: ['items'] }),
        Order.count(),
    ]);

    res.json({
        data: orders.map(o => ({
            id:             String(o.id),
            order_number:   o.orderNumber,
            status:         o.status,
            total:          parseFloat(o.total),
            currency:       o.currency || 'USD',
            customer_email: o.customerEmail,
            customer_name:  o.customerName,
            items:          o.items.map(i => ({
                product_id: String(i.productId),
                name:       i.name,
                quantity:   i.quantity,
                price:      parseFloat(i.price),
            })),
            created_at: o.createdAt.toISOString(),
        })),
        meta: {
            current_page: page,
            per_page:     perPage,
            total,
            last_page:    Math.ceil(total / perPage),
        },
    });
});

app.listen(3000);

PY Python / FastAPI

from fastapi import FastAPI, Header, HTTPException, Query
from typing import Optional
import os, math

app = FastAPI()
HIWELINK_API_KEY = os.getenv("HIWELINK_API_KEY", "")

def verify_key(x_api_key: str = Header(...)):
    if x_api_key != HIWELINK_API_KEY:
        raise HTTPException(status_code=401, detail="Invalid API key")

@app.get("/health")
def health(key=Depends(verify_key)):
    return {"status": "ok", "timestamp": datetime.utcnow().isoformat() + "Z"}

@app.get("/api/products")
def list_products(
    page: int = Query(1, ge=1),
    per_page: int = Query(50, ge=1, le=100),
    key=Depends(verify_key),
):
    total    = db.query(Product).filter_by(status="active").count()
    products = (db.query(Product)
                  .filter_by(status="active")
                  .offset((page - 1) * per_page)
                  .limit(per_page)
                  .all())
    return {
        "data": [
            {
                "id":             str(p.id),
                "name":           p.name,
                "description":    p.description,
                "sku":            p.sku,
                "price":          float(p.price),
                "stock_status":   "in_stock" if p.stock > 0 else "out_of_stock",
                "stock_quantity": int(p.stock),
                "categories":     p.categories or [],
                "images":         p.images or [],
            }
            for p in products
        ],
        "meta": {
            "current_page": page,
            "per_page":     per_page,
            "total":        total,
            "last_page":    math.ceil(total / per_page),
        },
    }

@app.get("/api/orders")
def list_orders(
    page: int = Query(1, ge=1),
    per_page: int = Query(50, ge=1, le=100),
    key=Depends(verify_key),
):
    total  = db.query(Order).count()
    orders = (db.query(Order).offset((page - 1) * per_page).limit(per_page).all())
    return {
        "data": [
            {
                "id":             str(o.id),
                "order_number":   o.order_number,
                "status":         o.status,
                "total":          float(o.total),
                "customer_email": o.customer_email,
                "customer_name":  o.customer_name,
                "items":          [{"product_id": str(i.product_id), "quantity": i.quantity, "price": float(i.price)} for i in o.items],
                "created_at":     o.created_at.isoformat() + "Z",
            }
            for o in orders
        ],
        "meta": {
            "current_page": page,
            "per_page":     per_page,
            "total":        total,
            "last_page":    math.ceil(total / per_page),
        },
    }

Webhooks (Real-Time Updates)

Instead of waiting for the next scheduled sync, configure your store to push changes to HiWelink instantly via webhooks. When a product or order is created, updated, or deleted in your store, fire a POST request to the HiWelink webhook endpoint.

Webhook Endpoint URLs

Products
POST https://www.hiwelink.com/api/generic-api/webhooks/{store_token}/products
Orders
POST https://www.hiwelink.com/api/generic-api/webhooks/{store_token}/orders
Bulk Sync
POST https://www.hiwelink.com/api/generic-api/webhooks/{store_token}/sync
Where to find your store_token: Navigate to Commerce Dashboard → select your store → the store token is shown on the detail page.

Supported Events

Product Events

product.created product.updated product.deleted

Order Events

order.created order.updated order.deleted

Webhook Payload Examples

Product created / updated
POST /api/generic-api/webhooks/{store_token}/products
Content-Type: application/json

{
  "event": "product.updated",
  "data": {
    "id":           "prod_123",
    "name":         "Wireless Headphones",
    "price":        349.99,
    "stock_status": "in_stock",
    "description":  "Updated description after firmware change.",
    "images":       ["https://yourstore.com/images/wh-v2.jpg"]
  }
}
Product deleted
{
  "event": "product.deleted",
  "data": {
    "id": "prod_123"
  }
}
Order created
POST /api/generic-api/webhooks/{store_token}/orders
Content-Type: application/json

{
  "event": "order.created",
  "data": {
    "id":             "order_789",
    "order_number":   "ORD-2024-0789",
    "status":         "pending",
    "total":          349.99,
    "customer_email": "bob@example.com",
    "customer_name":  "Bob Smith",
    "items": [
      { "product_id": "prod_123", "quantity": 1, "price": 349.99 }
    ]
  }
}

Expected Response

HiWelink responds with HTTP 200 on success. Your store should not retry if it receives 200. Retry on 500.

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "message": "Webhook processed"
}

Sending Webhooks from Your Store


use Illuminate\Support\Facades\Http;

class ProductObserver
{
    private string $webhookUrl = 'https://hiwelink.com/api/generic-api/webhooks/YOUR_STORE_TOKEN/products';

    public function updated(Product $product): void
    {
        Http::post($this->webhookUrl, [
            'event' => 'product.updated',
            'data'  => [
                'id'           => (string) $product->id,
                'name'         => $product->name,
                'price'        => (float) $product->price,
                'stock_status' => $product->stock > 0 ? 'in_stock' : 'out_of_stock',
            ],
        ]);
    }

    public function deleted(Product $product): void
    {
        Http::post($this->webhookUrl, [
            'event' => 'product.deleted',
            'data'  => ['id' => (string) $product->id],
        ]);
    }
}

Connect to HiWelink

Once your API endpoints are live and credentials are ready, follow these steps in the HiWelink dashboard.

  1. 1

    Navigate to Commerce Dashboard

    Go to your HiWelink dashboard → Commerce → click "Add Store".

  2. 2

    Select Generic API

    Choose "Generic API" from the store type list.

  3. 3

    Enter Base URL

    Provide the root URL of your API, e.g. https://api.yourstore.com. Do not include a trailing slash or endpoint path.

  4. 4

    Select Auth Method & Enter Credentials

    Choose API Key, Bearer Token, or Basic Auth. Paste the credential you generated on your backend.

  5. 5

    Configure Endpoints (optional)

    If your paths differ from the defaults, override them:

    Health endpoint: /health
    Products endpoint: /api/products
    Orders endpoint: /api/orders
  6. 6

    Configure Field Mappings (optional)

    If your field names differ from the defaults, provide a JSON mapping as described in the Field Mapping section.

  7. 7

    Click Connect

    HiWelink tests connectivity by calling /health. On success, the store is activated and the initial product and order sync is dispatched automatically.

  8. 8

    Monitor Sync Progress

    Watch the Sync Runs section on the store detail page. Once products are synced and embeddings are generated, your AI chatbot is ready.

Security Best Practices

Follow these guidelines to keep your store API secure when opening it to HiWelink.

1

Always use HTTPS

Never expose API endpoints over plain HTTP. TLS ensures credentials and data cannot be intercepted in transit. HiWelink rejects non-HTTPS URLs for production stores.

2

Scope credentials to read-only

Create a dedicated API key or user for HiWelink with read-only access to products and orders only. HiWelink never writes data to your store.

3

Never store raw API keys in plain text

Hash keys with bcrypt before storing in your database. Show the raw key only once at creation time.

4

Rate-limit your endpoints

Apply rate limiting (e.g. 100 requests per minute per API key) to prevent accidental overload. HiWelink respects 429 Too Many Requests and will retry with backoff.

5

Whitelist HiWelink IP addresses (optional)

For extra security, restrict the API endpoints to only accept traffic from HiWelink servers at the firewall or Nginx level.

6

Rotate credentials regularly

Generate a new API key every 90 days. Disconnect and reconnect your store in HiWelink with the new key. Immediately revoke the old key.

7

Log and monitor API access

Log every request to your API endpoints including IP, timestamp, and key fingerprint. Alert on unusual access patterns.

8

Validate webhook payloads (if adding a signature)

When sending webhooks from your store, optionally include an HMAC-SHA256 signature in the X-Webhook-Signature header. Verify it on both sides to prevent spoofed webhook calls.

Troubleshooting

Common integration issues and how to resolve them.

Connection test fails — "Could not connect to API"

Check:

  • The Base URL is correct and reachable from the internet.
  • The /health endpoint returns HTTP 200.
  • HTTPS is configured with a valid TLS certificate (not self-signed).
  • No firewall or security group is blocking inbound traffic on port 443.
  • The health endpoint does not require any query parameters.
curl -I -H "X-API-Key: YOUR_KEY" https://api.yourstore.com/health
HTTP 401 Unauthorized on every request
  • Verify the credential entered in HiWelink exactly matches what your backend validates. No extra spaces or newlines.
  • For API Key: confirm the header name is X-API-Key (case-sensitive on some frameworks).
  • For Bearer Token: ensure your middleware strips the "Bearer " prefix before comparing.
  • For Basic Auth: verify the username and password are correct. Note: colons in the password must be URL-encoded.
  • Disconnect the store, update the credential, and reconnect.
Sync completes but 0 products are imported
  • The products endpoint must return an array under the "data" key, or a flat JSON array.
  • Each product must have an "id" (or alias) and "name" (or alias) field.
  • Test the endpoint manually:
curl -H "X-API-Key: YOUR_KEY" "https://api.yourstore.com/api/products?page=1&per_page=1"
  • If your field is named differently (e.g. "product_title" instead of "name"), configure field mappings.
  • Check the sync run error log in the Commerce Dashboard for per-product error messages.
Sync gets stuck / only first page is imported
  • Verify your endpoint correctly handles the page and per_page query parameters.
  • Confirm that when the last page is requested, you return fewer items than per_page (not an empty 404).
  • If page 2 returns the same results as page 1, your pagination logic has a bug.
  • Test sequentially:
curl "…/api/products?page=1&per_page=50"   # should return 50 items
curl "…/api/products?page=2&per_page=50"   # should return next 50
curl "…/api/products?page=999&per_page=50" # should return 0 or fewer items
Webhook not received by HiWelink
  • Verify the store_token in the webhook URL matches the token shown in your Commerce Dashboard.
  • Ensure you are sending Content-Type: application/json.
  • The "event" field must exactly match one of: product.created, product.updated, product.deleted, order.created, order.updated, order.deleted.
  • The "data" object must include an "id" field.
  • Check the Webhooks tab on your store detail page — failed webhooks and their payloads are logged there.
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"event":"product.updated","data":{"id":"prod_1","name":"Test"}}' \
  https://hiwelink.com/api/generic-api/webhooks/YOUR_STORE_TOKEN/products
Chatbot cannot answer product questions after sync
  • Verify the product sync completed successfully (check Sync Runs in the dashboard).
  • Confirm the chatbot is linked to this commerce store in chatbot settings.
  • Wait for embedding generation to complete — this runs after the product sync and can take several minutes for large catalogs.
  • If products have very short names and no description, the AI has limited data to answer questions. Add descriptions to improve chatbot quality.
  • Use "Re-embed Products" on the store detail page to regenerate embeddings after updating product data.
Sync job fails with timeout error
  • Each page request times out after 30 seconds. Your products endpoint must respond within this window.
  • Reduce per_page if your server is slow to query large datasets.
  • Add a database index on the columns used in your ORDER BY clause for pagination.
  • HiWelink retries failed requests up to 3 times with exponential backoff (1s, 2s, 4s). If all retries fail, the sync run is marked as failed.
  • Trigger a manual sync from the dashboard after fixing the underlying issue.

Still stuck? Open the sync run in your dashboard to read the detailed error message. For further support, include the sync run ID and a curl reproduction case when contacting support.