You'll get an email with a reset password link.
Back to Sign In
Connect any e-commerce platform to HiWelink. Implement REST endpoints, secure with authentication, and sync products & orders for an AI-powered shopping assistant.
The Generic API connector lets you integrate any REST-capable e-commerce platform with HiWelink. Follow these four steps to get started.
Add /health, /api/products, and /api/orders endpoints to your store backend. Paths are fully customizable.
Create a dedicated API key, bearer token, or basic-auth user specifically for HiWelink. Scope it read-only.
In the Commerce Dashboard, add a Generic API store. Enter your base URL, auth credentials, and endpoint paths.
Trigger an initial product and order sync. Optionally configure webhooks for real-time updates. Your chatbot is ready.
End-to-end data path from setup through live chatbot usage.
Your API must expose the following three endpoints. All endpoints must:
/health
Default path (customizable)
Health check endpoint. HiWelink calls this during connection and reconnection to verify your API is reachable. Must return HTTP 200.
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00Z"
}
/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
{
"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
}
}
id,
name.
HiWelink will skip any product missing both fields. All other fields are optional but recommended for richer chatbot answers.
/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
}
}
id.
All other fields are optional. Provide as much detail as possible to enable order-status queries from the chatbot.
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.
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
Read the X-API-Key header value and compare it against a stored secret. Reject with HTTP 401 if it does not match.
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
A long, randomly generated string stored securely. Validate by direct comparison.
A signed JSON Web Token. Validate the signature and expiry claim using your secret key.
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")
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.
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.
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.
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.
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.
HiWelink sends the token as
X-API-Key: <token>.
Your store reads that header and compares the value against what you configured.
X-API-Key: sk_live_4f8d2a1b9c3e7f6a...
You can use the generated token above as this value.
HiWelink sends the same token as Authorization: Bearer <token>.
The token value is identical — only the header format differs from API Key.
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.
HiWelink encodes username:password
in Base64 and sends it as Authorization: Basic <encoded>.
You can use the generated token as the password.
hiwelinksk_live_4f8d2a1b9c3e7f6a... ← use generated tokenAuthorization: Basic Ym90bWVyemU6c2tfbGl2ZV8...
Once you have your credentials, you need to configure three things inside your e-commerce store:
| 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. |
HIWELINK_API_KEY=sk_live_87db8f090a2544ac2bd658ebd6f042db...
HIWELINK_WEBHOOK_URL=https://app.hiwelink.com
HIWELINK_STORE_TOKEN=88bedac0-a8da-4700-8d07-5dbbf4fdfd6d
<?php
// config/hiwelink.php
return [
'api_key' => env('HIWELINK_API_KEY', ''),
'webhook_url' => env('HIWELINK_WEBHOOK_URL', ''),
'store_token' => env('HIWELINK_STORE_TOKEN', ''),
];
<?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']);
});
<?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,
],
]);
}
}
<?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);
HIWELINK_API_KEY=sk_live_87db8f090a2544ac2bd658ebd6f042db...
HIWELINK_WEBHOOK_URL=https://app.hiwelink.com
HIWELINK_STORE_TOKEN=88bedac0-a8da-4700-8d07-5dbbf4fdfd6d
// 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);
// 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);
HIWELINK_API_KEY=sk_live_87db8f090a2544ac2bd658ebd6f042db...
HIWELINK_WEBHOOK_URL=https://app.hiwelink.com
HIWELINK_STORE_TOKEN=88bedac0-a8da-4700-8d07-5dbbf4fdfd6d
# 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(): ...
# 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.
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.
{
"id": "42",
"name": "Blue Hoodie",
"permalink": "https://store.com/product/blue-hoodie"
}
{
"id": "42",
"name": "Blue Hoodie",
"slug": "blue-hoodie"
}
{
"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.
|
| 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 |
HiWelink fetches data page-by-page to handle large catalogs without timeouts. Default page size is 50 items. The request stops when a page returns fewer items than requested.
page — current page number, starting from 1per_page — items per page (default: 50, max: 100){
"data": [ { "id": "1", "name": "..." }, ... ],
"meta": {
"current_page": 1,
"per_page": 50,
"total": 250,
"last_page": 5
}
}
[ { "id": "1", "name": "..." }, { "id": "2", "name": "..." }, ... ]
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.
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.
{
"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"
}
in_stock, instock, available,
true, 1
out_of_stock, outofstock, unavailable,
false, 0
349.99 — number"349.99" — numeric string"$349.99" — currency prefix stripped"USD 349.99" — currency prefix strippedFull working implementations for popular backend frameworks. Each example includes the health endpoint, products, orders, and API key authentication middleware.
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(),
],
]);
});
});
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);
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),
},
}
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.
POST
https://www.hiwelink.com/api/generic-api/webhooks/{store_token}/products
POST
https://www.hiwelink.com/api/generic-api/webhooks/{store_token}/orders
POST
https://www.hiwelink.com/api/generic-api/webhooks/{store_token}/sync
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"]
}
}
{
"event": "product.deleted",
"data": {
"id": "prod_123"
}
}
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 }
]
}
}
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"
}
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],
]);
}
}
Once your API endpoints are live and credentials are ready, follow these steps in the HiWelink dashboard.
Navigate to Commerce Dashboard
Go to your HiWelink dashboard → Commerce → click "Add Store".
Select Generic API
Choose "Generic API" from the store type list.
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.
Select Auth Method & Enter Credentials
Choose API Key, Bearer Token, or Basic Auth. Paste the credential you generated on your backend.
Configure Endpoints (optional)
If your paths differ from the defaults, override them:
Configure Field Mappings (optional)
If your field names differ from the defaults, provide a JSON mapping as described in the Field Mapping section.
Click Connect
HiWelink tests connectivity by calling /health. On success, the store is activated and the initial product and order sync is dispatched automatically.
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.
Follow these guidelines to keep your store API secure when opening it to HiWelink.
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.
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.
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.
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.
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.
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.
Log and monitor API access
Log every request to your API endpoints including IP, timestamp, and key fingerprint. Alert on unusual access patterns.
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.
Common integration issues and how to resolve them.
Check:
curl -I -H "X-API-Key: YOUR_KEY" https://api.yourstore.com/health
curl -H "X-API-Key: YOUR_KEY"
"https://api.yourstore.com/api/products?page=1&per_page=1"
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
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
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.