Order API and webhooks
Send orders from your own website or ERP to the Production Floor, and receive signed webhooks as lines are mapped, jobs move and orders ship.
If your orders live in your own website or ERP rather than Shopify or WooCommerce, your software can send them straight to your Production Floor, and Yantra Bhavan can call your software back as the work moves: when a line is mapped, when a job changes status and when it ships.
Sending orders in
Your software needs a third-party credential with order access. An Owner or Manager opens Integrations, chooses Connect → Third-party software, and ticks Can create orders. A credential without that tick can read quotations but is refused on the order routes. How a credential becomes an access token is in building an integration. Order intake needs a plan with both integrations and the Production Floor.
curl -X POST "https://shriganesh.yantrabhavan.com/api/v1/integration/orders" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: 9b2f6c1e-3d4a-4f7b-8c55-0e1a2b3c4d5e" \
-H "Content-Type: application/json" \
-d '{
"externalId": "SO-10442",
"number": "#10442",
"version": 1760342400000,
"dueAt": "2026-10-20T00:00:00Z",
"lines": [
{ "lineId": "L1", "sku": "BRACKET-PLA-01",
"title": "Mounting bracket, black PLA", "quantity": 4, "unitPrice": 120 }
]
}'Idempotency-Keyis required: a fresh random value (a UUID) per order you send. If the request times out, send it again with the same key and body — you get the original answer back and no second order. The same key with a different body is refused with 422. Keys are remembered for 30 days.externalIdis your order id. Posting it again (with a new key) updates that order instead of creating another. Send a risingversion— for example your updated-at time in milliseconds — so an older update arriving late is ignored. Setcancelledto true to cancel.- The answer is 201 for a new order and 200 for an update, with the order's intake status and, per line, whether it has been mapped and which jobs it has.
GET …/orders/returns the same shape later. - The order lands in Store orders next to your Shopify and WooCommerce orders. A line whose SKU has a rule goes to the floor by itself; any other line waits for you to map it once — see orders from your online store.
Adding a webhook endpoint
In Integrations, under Webhooks, an Owner or Manager adds the URL Yantra Bhavan should call and picks the events it wants. The URL must be https:// on the standard port and reach the public internet — addresses inside a private network are refused, both when you save it and on every delivery.
quotation.requested— A buyer asked your shop for a quotation.external_order.line_mapped— A store or API order line was given a process — by a SKU rule or by hand.production_job.status_changed— A production job moved from one status to another (queued, in progress, done…).shipment.created— A shipment was created for one of your orders, with carrier and tracking.
What a delivery looks like
Each event is a JSON POST. The body carries ids, statuses and tracking — never your buyer's contact details. When the order came from your system, external_id and line_id are your own ids, so you can match it without a lookup.
{
"id": "0d6f2b1e-7c1a-4b8e-9a51-3f2e4d5c6b7a",
"type": "production_job.status_changed",
"created_at": "2026-10-13T09:30:00.000Z",
"shop_id": "…",
"data": {
"job_id": "…", "from": "READY", "to": "IN_PROGRESS",
"order_id": "…", "order_item_id": "…", "source": "API",
"external_id": "SO-10442", "line_id": "L1"
}
}X-YB-Event-Id— the event id, the same asidin the body.X-YB-Event-Type— the event type.X-YB-Timestamp— when it was signed, in Unix seconds.X-YB-Signature—v1=followed by the hex HMAC-SHA256 of the timestamp, a full stop, and the raw body, keyed with your wholewhsec_secret.
Verifying a delivery
Check every delivery before you trust it. Four rules, all in the snippets below:
- Sign the raw request body, byte for byte. Parsing the JSON and serialising it again changes it, and the signature will not match.
- Compare signatures in constant time (
crypto.timingSafeEqual,hash_equals,hmac.compare_digest), never with==or===. - Refuse a timestamp more than five minutes from your clock, so a captured delivery cannot be replayed later.
- Remember event ids you have handled and skip repeats. A retry carries the same id, and in rare cases one event arrives twice.
Node.js
const crypto = require('node:crypto');
// Express: app.post('/yb-webhooks', express.raw({ type: 'application/json' }), handler)
// req.body must be the RAW bytes — re-serialised JSON will not verify.
function verifyYantraBhavan(secret, req) {
const timestamp = req.get('X-YB-Timestamp') || '';
const signature = req.get('X-YB-Signature') || '';
if (!/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
'v1=' +
crypto.createHmac('sha256', secret)
.update(timestamp + '.')
.update(req.body)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}PHP
<?php
$secret = getenv('YB_WEBHOOK_SECRET'); // the whole whsec_… value
$payload = file_get_contents('php://input'); // raw body
$timestamp = $_SERVER['HTTP_X_YB_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_YB_SIGNATURE'] ?? '';
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(400);
exit;
}
$expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(400);
exit;
}
http_response_code(204); // accept now, do the work afterwardsPython
import hashlib
import hmac
import time
def verify_yantra_bhavan(secret: str, headers, raw_body: bytes) -> bool:
timestamp = headers.get("X-YB-Timestamp", "")
signature = headers.get("X-YB-Signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
mac = hmac.new(
secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest("v1=" + mac, signature)
# Flask: verify_yantra_bhavan(secret, request.headers, request.get_data())Retries, and when an endpoint is switched off
Answer with any 2xx within 10 seconds and do slow work afterwards. Anything else — an error status, a timeout, a redirect — counts as a failure, and that event is tried again up to eight times, waiting 1, 2, 4 … 64 minutes in between (about two hours in all).
If an endpoint keeps failing for 24 hours, it is switched off and the shop owner gets an email. It shows as Disabled in Integrations; fix the receiver, use Send test event to check it, then Re-enable. Events from while it was off are not sent again — use GET …/orders/ to catch up. Recent deliveries lists each attempt with its status code and error.
Read next
- Orders from your online storeConnect a Shopify store, map each order line to a process and machine once, follow it on the floor, and ship with tracking that goes back to the store.
- The Production FloorThe floor assigns work to idle machines by itself. You confirm what comes off them, and deal with what needs you.