Guide to BaseLinker Webhooks: Features and Best Practices
BaseLinker (rebranded Base.com) is a Polish multichannel e-commerce platform for order management, warehouse and inventory, and integrations with marketplaces, stores, and couriers. Its webhook-style callbacks notify your systems when something changes on an order, and they behave unlike almost every other provider you'll integrate.
This guide covers the three things that make BaseLinker callbacks unusual (HEAD transport, query-string payload, no verification), how to wire a handler that survives them, and the best practices for production.
What are BaseLinker webhooks?
BaseLinker publishes no public webhook documentation at all. Its public API (api.baselinker.com, roughly 195 methods over connector.php) is strictly request/response, with change tracking done by polling (getJournalList, getOrderReturnJournalList, getInventoryProductLogs), and neither the English nor the Polish help centre documents an outbound webhook. Everything known about the wire format is observed from real deliveries, not documented.
BaseLinker delivers callbacks as HTTP HEAD requests, which by definition carry no body. The entire payload travels in the query string, and there is no signature, secret, or handshake of any kind.
BaseLinker webhook features
| Feature | Details |
|---|---|
| Transport | HTTP HEAD, not POST; the request has no body by definition |
| Payload | Entirely in the query string; observed params are order_id and state (undocumented, not exhaustive) |
| Signature | None. No HMAC, no signature header, no timestamp, no shared secret, no challenge step |
| Response | A bare 200 with no body (a HEAD response must not carry one, per RFC 9110) |
| Documentation | None; the wire format is observed, not published |
| Order detail | Not in the callback; fetch it from the API (getOrders, authenticated with X-BLToken, 100 requests/minute) |
| SDK | None |
What the callback carries
The query parameters observed on real deliveries are:
| Param | Observed example | Notes |
|---|---|---|
order_id | 42 | A string on the wire; coerce with Number(...) before use |
state | packed | An opaque string, not a documented enum or event-type discriminator |
HEAD /webhooks/baselinker?order_id=42&state=packed HTTP/1.1
Host: your-app.example.com
For background on what changes exist in the platform, BaseLinker's Automatic Actions system events cover orders fetched, paid, and confirmed, status changes, shipments created or deleted, courier parcel status changes, invoices and receipts, returns, PickPack collecting and packing, and marketplace cancellations.
See BaseLinker webhook requests in action. Inspect and replay sample BaseLinker requests in the Hookdeck Console — no account or setup required.
Setting up BaseLinker webhooks
Because the callback carries no detail, the working pattern is notify-then-fetch: acknowledge the HEAD, then look the order up through the API.
curl -X POST https://api.baselinker.com/connector.php \
-H 'X-BLToken: YOUR_API_TOKEN' \
-d 'method=getOrders' \
--data-urlencode 'parameters={"order_id":42}'
X-BLToken is your API token for outbound requests to BaseLinker, rate-limited at 100 requests/minute. It's worth stating plainly: it is a request header for calls you make, never a webhook signature on inbound deliveries.
The callback is also undocumented and not guaranteed to cover every transition, so for complete change tracking, poll getJournalList with a last_log_id cursor and treat the callback as a low-latency hint.
For local development, use the Hookdeck CLI (hookdeck listen 3000 baselinker --path /webhooks/baselinker) to get a public HTTPS URL plus an inspector. When you create a Baselinker source in Hookdeck, its allowed HTTP methods are seeded to ["HEAD"]; that's an initial default you can edit, not an enforced setting.
Handling BaseLinker webhooks
Register the HEAD route explicitly
This is the part everyone gets wrong. A JSON body parser has nothing to parse, req.body is always empty, and a POST route never fires:
| Framework | Correct | Wrong |
|---|---|---|
| Express | app.head("/webhooks/baselinker", handler), read req.query | app.post(...), express.json() on the route, req.body |
| Next.js (App Router) | export async function HEAD(request), read request.nextUrl.searchParams | exporting POST, await request.json() |
| FastAPI | @app.head("/webhooks/baselinker"), typed query args | @app.post(...), a Pydantic body model |
Express's app.get() also answers HEAD requests, but register app.head() explicitly so the intent is visible and a future refactor can't change the behaviour.
Respond with a bare 200
A HEAD response must not carry a body (RFC 9110 §9.3.2), so acknowledge with a status code and nothing else:
const crypto = require("crypto");
// OPTIONAL, and not a BaseLinker signature: a token you appended to the
// endpoint URL yourself, echoed back in the query string
function verifyUrlToken(query, expected) {
if (!expected) return true;
const provided = query.token;
if (typeof provided !== "string") return false;
const a = Buffer.from(provided), b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.head("/webhooks/baselinker", (req, res) => {
if (!verifyUrlToken(req.query, process.env.BASELINKER_URL_TOKEN)) {
return res.sendStatus(401);
}
// Query values are always strings; either param may be absent
const orderId = req.query.order_id ? Number(req.query.order_id) : null;
const state = req.query.state ?? null;
if (orderId) {
processQueue.add({ orderId, state }); // fetch detail via getOrders, async
}
res.sendStatus(200); // bare 200: a HEAD response must not carry a body
});
One consequence of the bodyless response: when you route BaseLinker through Hookdeck, the request ID comes back in the x-hookdeck-request-id response header rather than in a body, and that header is how you correlate a delivery with its dashboard entry.
Securing BaseLinker webhooks
There is nothing to verify. BaseLinker provides no cryptographic authentication for these callbacks: no HMAC, no signature header, no timestamp, no shared secret, and no handshake (a BaseLinker HEAD goes straight to ingestion).
What you can do is defence in depth, none of it provided by the platform:
- Endpoint-URL secrecy. Use a long, unguessable path and never log the full URL.
- A token you append yourself. You control the URL you register, so add your own
?token=<random>and compare it timing-safely (as in the sample above). That's your secret round-tripped back to you, not a BaseLinker signature. - Network controls. TLS only, a WAF or rate limit in front, and IP restriction if you can establish source IPs for your account (BaseLinker publishes no allowlist).
- Treat the callback as a hint. Since the ping is unauthenticated, fetch the authoritative state from the API before acting on it.
Make BaseLinker webhooks production-ready. Hookdeck Event Gateway gives you a dedicated ingestion URL with full request logging, deduplication, and controlled delivery to your handler.
BaseLinker webhook limitations and pain points
The callback is undocumented
The Problem: There's no official reference for the wire format, the parameter list, or which transitions fire a callback, so every assumption in your handler rests on observed behaviour.
Why It Happens: BaseLinker's platform is built around its request/response API with polling-based change tracking; the callback exists but isn't part of the documented surface.
Workarounds:
- Treat
order_idandstateas optional observed params, and pollgetJournalListwith alast_log_idcursor where completeness matters.
How Hookdeck Can Help: Every delivery is logged with its full URL, headers, and query params, so you can see exactly what BaseLinker sends your account rather than guessing from a spec that doesn't exist.
No signature verification exists
The Problem: Anyone who learns your endpoint URL can fake a callback, and there's no signature to reject it with.
Why It Happens: The platform provides no webhook authentication mechanism; in Hookdeck's API spec, the Baselinker source's auth schema is empty, in the same zero-property cohort as AWS SNS, Monday, and Strava.
Workarounds:
- Keep the URL secret, append your own token as a query param, and fetch authoritative order state from the API instead of trusting the ping.
How Hookdeck Can Help: A dedicated ingestion URL narrows what you expose, and filters can drop requests missing your self-appended token before they reach your handler.
HEAD requests break body-parsing assumptions
The Problem: Handlers written for normal webhooks read req.body and get nothing, mount JSON parsers with nothing to parse, register POST routes that never fire, or return JSON bodies that violate the HEAD response rule.
Why It Happens: HEAD is a metadata method; almost no other webhook provider uses it as a delivery transport, so framework defaults and muscle memory both point the wrong way.
Workarounds:
- Register HEAD routes explicitly, read the query string, skip body parsers on the route, and respond with a bare 200.
How Hookdeck Can Help: The dashboard shows the request exactly as received, method included, which makes "why is my body empty" a thirty-second diagnosis instead of a debugging session.
The notification carries no detail
The Problem: A callback tells you that order 42 changed, not what changed, so every notification costs an API round-trip against a 100 requests/minute limit, and a busy store can burn through that during a spike.
Why It Happens: The callback is a thin ping; order data lives behind the request/response API.
Workarounds:
- Queue the fetch-backs and batch or coalesce lookups for the same order rather than calling
getOrdersinline per callback.
How Hookdeck Can Help: Set a delivery rate on the connection so callbacks reach your handler at a pace that keeps your fetch-backs under BaseLinker's API limit, with the burst absorbed by the queue.
Best practices
Register HEAD explicitly and skip the body parser
Route the exact method BaseLinker sends, and don't mount JSON parsing on a route that never has a body.
Treat every param as optional and every value as a string
Guard for absence and coerce order_id explicitly. The observed params aren't a contract.
Respond with a bare 200
No JSON, no body, just the status code.
Add your own URL token
BaseLinker gives you no secret, but you control the URL you register. Append ?token=<random> and compare timing-safely.
Fetch state from the API, poll for completeness
Use getOrders for detail after each ping, and getJournalList with a cursor where you can't afford to miss a transition.
Dedupe on order and state
The same notification can arrive more than once. Key idempotency on order_id + state. See our guide to webhook idempotency.
Conclusion
BaseLinker callbacks are bodyless HTTP HEAD requests with the payload in the query string, no public documentation, and no signature verification, so the safe pattern is a HEAD-registered route that acknowledges with a bare 200, checks a token you appended yourself, and fetches authoritative order state from the API. Where completeness matters, poll getJournalList and treat the callback as a latency optimization.
Hookdeck Event Gateway logs every request as received, filters unauthenticated noise, absorbs bursts, and delivers at a rate that respects BaseLinker's API limits, so an undocumented callback becomes an observable, controlled part of your integration.
Get started with Hookdeck for free and handle BaseLinker webhooks reliably in minutes.