Zentail Store Management
Zentail Store Management connects an Odoo database to a Zentail account through Zentail's Open API. Everything on this page describes the zentail_store_management module as it ships today; the behaviour below was read out of the released build.
Zentail is the catalogue master in this relationship. The connector does not try to mirror Zentail's catalogue into Odoo: it exports product data as a report, pushes product data as a CSV you prepare, and reads inventory, listings and orders. That is the surface Zentail's merchant API actually documents, and this module implements exactly that surface — nothing more, and no invented routes.
| Technical name | zentail_store_management |
| Odoo versions | 17.0, 18.0, 19.0 (Community or Enterprise) |
| Current shipped version | 17.0 → 17.0.2.0.2, 18.0 → 18.0.2.0.2, 19.0 → 19.0.2.0.2 |
| Price | $499 USD — one-time, per Odoo version |
| Odoo module licence | OPL-1 |
| Category | Connector |
What the module does, and what it does not
It does:
- connect to Zentail with the API token from Account Settings → API Access, and verify that token from Odoo with two independent tests;
- export a product report (
POST /v1/reportwithisExport), wait for it to complete, and keep the generated CSV as an Odoo attachment; - push a product CSV you prepare (
POST /v1/report), creating new SKUs and optionally clearing blank fields; - read inventory per SKU — the all-warehouse total and the per-warehouse breakdown;
- read listings and orders;
- receive Zentail webhooks over four signed endpoints and turn them into queue jobs;
- keep SKU-to-product, warehouse and payment-gateway mappings, with a dashboard over the records it holds.
It deliberately does not:
- write products, orders, customers, refunds, payouts, shipments, locations or webhooks. Zentail publishes no merchant endpoint for those; the paths a previous build called were invented, and they have been removed rather than left to fail against the real API.
- import product records into Odoo as products. Product data moves through the report endpoint in both directions; the mapping table links a Zentail SKU to the Odoo product you already created.
- pull sales orders on a schedule. There is no order-list import. An order payload is available read-only from the Operations Center, and order records can arrive through the webhook intake described below.
- write stock quantities into Odoo. Zentail documents the inventory paths but not the response schema for them, so the connector records what the API returned on the sync log and leaves your Odoo stock levels alone. A quantity derived from an unpublished shape would be an invented number.
If a capability is not on the first list, the module does not have it.
Requirements
| Requirement | Detail |
|---|---|
| Odoo | 17.0, 18.0 or 19.0, Community or Enterprise. Self-hosted or Odoo.sh — Odoo Online (SaaS) cannot install third-party modules |
| Odoo apps | base, sale_management, stock, account, delivery, mail, web, portal, product, contacts, digest — Odoo installs any that are missing |
| Python packages | requests |
| Zentail account | An account with API access, and a token generated under Account Settings → API Access |
| Network | Outbound HTTPS to api.zentail.com (and to the report file host Zentail returns in filePath) |
| Inbound (webhooks only) | Your Odoo instance reachable over HTTPS from Zentail, if you use the webhook intake |
Install requests into the same interpreter that runs Odoo:
sudo -u odoo pip install requests
Installation
- In Odoo, open Apps, turn on developer mode, and click Update Apps List.
- Search for Zentail Store Management and click Install.
- Odoo resolves the apps listed above by itself. There is no second package to fetch and no extra download step — the module is self-contained.
When the install finishes, a Zentail app appears in the main Odoo app switcher.
For a self-hosted server that installs the module from a package, follow the usual Odoo custom-addon procedure: the folder zentail_store_management/ must sit directly inside your addons path with __manifest__.py at its top level, then restart Odoo and update the apps list.
Connect your Zentail account
1. Create the API token in Zentail
In Zentail, open Account Settings → API Access and choose GENERATE NEW TOKEN. Copy the value; Zentail shows it once.
2. Create the instance record in Odoo
Go to Zentail → Configuration → Instances → New.
| Field | What it is | Required |
|---|---|---|
| Instance Name | A label for this connection inside Odoo | Yes |
| Account Reference | A short label identifying this Zentail account in Odoo (for example mystore). It is not sent in any API request | Yes |
| Company | The Odoo company this connection belongs to | Yes |
| API Base URL | Defaults to https://api.zentail.com. Change it only if Zentail moves the API or you test against a staging host | No |
| Auth Header | Which HTTP header carries the token — see step 3 | Yes |
| Access Token | The token from step 1. This is what authenticates every request | Recommended |
| API Key / Client ID | Only for the registered-app OAuth flow: Zentail issues a client ID and secret, the token exchange is completed with Zentail outside Odoo, and the resulting token is pasted into Access Token | No |
| Client Secret | The paired secret from the same registered-app flow. Also used as the HMAC key for webhooks | Only for webhooks |
| API Password | Legacy field, kept for older databases. Zentail retired private-app passwords in 2022 — use the access token | No |
| Notes | Free text | No |
The same form carries your operating defaults: default warehouse, discount and shipping products, the three risk thresholds (critical / high / medium), monthly marketing cost and monthly operating expenses, payout journal and currency. They feed the dashboard and the risk scoring; they are not sent to Zentail.
3. Set the auth header
Zentail does not publish the header name its API expects, so the connector makes it a setting instead of guessing:
| Setting | Sends |
|---|---|
| X-Auth-Token (default) | X-Auth-Token: <token> |
| Authorization: Bearer | Authorization: Bearer <token> |
Leave it on the default first. If Test Connection answers Authentication rejected while you are sure the token is correct, switch this setting and test again — that is the only variable between the two attempts.
4. Test the connection
The instance form has two test buttons, because Zentail's account endpoint is not confirmed for every account:
- Test Connection calls
GET /v1/accountand reports the account payload. - Test Report Access calls
POST /v1/reportwith an export of a singleSKUcolumn. It writes nothing to your Zentail account, and it is the dependable test when the account endpoint is unavailable.
Either test sets the instance Status to Connected on success or Error on failure, and raises the API's own message on failure so you can see the real cause rather than a generic error. A connection record that has never passed a test stays in Draft, and the scheduled actions skip it.
The Operations Center
Zentail → Operations → Run Operations is where every call the API documents is executed by hand. Choose an instance, choose an operation, fill in its fields and click Run.
| Operation | Calls | Fields |
|---|---|---|
| Export Product Report | POST /v1/report with isExport, then poll GET /v1/report/{reportId}, then fetch the file | Report Columns — comma-separated headers, first column must be SKU |
| Push Product Report | POST /v1/report | Product CSV (first header must be SKU), Create New Products, Overwrite With Blank |
| Read Inventory for a SKU | GET /v1/inventory/{sku} and GET /v1/inventory/{sku}/warehouses | SKU |
| Read Listings | GET /v1/listings | — |
| Read Orders | GET /v1/salesOrder | Order Parameters — optional key=value pairs, passed through verbatim |
How a product report works
Zentail's product mechanism is a report, not a REST resource, and the connector follows it exactly:
- A report is created with a CSV whose first column header is
SKU. That header row is also how you select which columns come back — the available column names are Zentail's own Data Dictionary, and the module does not assume any of them, so you name the ones you want. - The module polls the report every 5 seconds, for up to 5 minutes, until Zentail reports
COMPLETED. A report endingFAILED,ERRORorCANCELLEDstops with that status; a report that is still running at the timeout stops with the last status it reported. - A completed export carries a
filePath. That URL is fetched as it is (it lives on a different host from the API) and the file is stored as an Odoo attachment on the run.
A push is the same endpoint without the export flag: the CSV you paste is Base64-encoded and sent. Create New Products allows Zentail to create SKUs that do not exist yet. Overwrite With Blank clears fields that would otherwise be left untouched when the CSV has them empty.
How the reads behave
Inventory, listing and order reads record the API's own response on the sync log, together with the API's referenceId when it returns an error, so a support case can quote it. Inventory and listing reads do not change any Odoo record — see What the module does not do above. Order parameters are not published by Zentail either, so anything you type in that field is passed through unchanged rather than filtered against a list this module made up.
Webhooks
If you want Zentail to push events into Odoo, point it at these URLs on your instance. All four accept POST, require no user session, and are protected by an HMAC signature:
| Event | URL |
|---|---|
| Product | https://<your-odoo>/zentail/webhook/product |
| Order | https://<your-odoo>/zentail/webhook/order |
| Customer | https://<your-odoo>/zentail/webhook/customer |
| Refund | https://<your-odoo>/zentail/webhook/refund |
Each request must carry:
X-Zentail-Shop-Domain— used to find the matching instance: the module matches it against the instance's Account Reference.X-Zentail-Hmac-SHA256— the request body signed with your Client Secret (HMAC-SHA256, Base64-encoded). A request with a missing or mismatched signature is rejected and logged, and the queue job is not created.
A verified request is not processed inline. It becomes a queue job and a log entry, and the Process Queue Jobs scheduled action picks it up. That keeps a slow downstream action from timing out Zentail's request.
There are no separate settings to enable per event: the four routes exist as soon as the module is installed. The Webhooks menu under Configuration holds webhook records for your own reference and testing.
Scheduled actions
The module installs these scheduled actions (Settings → Technical → Scheduled Actions):
| Scheduled action | Runs every | On after install |
|---|---|---|
| Zentail: Sync Inventory | 10 minutes | Enabled — reads inventory for mapped SKUs and stamps the mapping |
| Zentail: Process Queue Jobs | 5 minutes | Enabled — drains the webhook and manual-job queue |
| Zentail: Process Workflows | 10 minutes | Enabled — runs the workflow rules |
| Zentail: Auto Risk Detection | 30 minutes | Enabled — scores the risk records |
| Zentail: Generate Analytics | 4 hours | Enabled — rebuilds the analytics records |
| Zentail: Webhook Health Check | 60 minutes | Enabled — flags a webhook that has stopped arriving |
| Zentail: Cleanup Old Logs | 24 hours | Enabled — prunes old log rows |
| Zentail: Daily Summary Report | 1 day | Enabled — the daily digest |
| Zentail: Sync Customers (no API endpoint — disabled) | 60 minutes | Disabled — Zentail exposes no customer endpoint; the action is kept so the name resolves, and it says so in the log instead of reporting an empty success |
Adjust the intervals to your volume. Zentail sets the API rate limits, not Odoo, and shortening an interval is the usual cause of throttling in the log. Every scheduled action skips cleanly when no instance is in the Connected state, so an unconfigured database does not fill its log with failures.
Menu and records
Zentail is a single app menu with these entries:
- Dashboard — the KPI view described below.
- Orders, Refunds, Products, Customers, Payouts — the record views the connector keeps. Orders and customers can be created by the webhook intake; refunds and payouts are local records. The API has no order-list, refund-list or payout-list import, so these views do not fill themselves from Zentail.
- Analytics — the generated analytics records.
- Operations — Run Operations, Queue Jobs, Logs.
- Configuration — Instances, Locations (Zentail warehouse ↔ Odoo warehouse mapping), Payment Gateways, Scheduled Actions, Webhooks.
Dashboard
The instance form and the dashboard show counts and sums over the records Odoo holds: products, orders, customers, queue jobs, errors, pending orders, return rate, average order value and out-of-stock products, with a period selector (and an explicit date range) plus a sales chart.
Read it as an Odoo-side view, not as a Zentail report. If you have not received orders through webhooks, the order figures are zero — that is the connector being honest about what it has, not a failed sync.
Troubleshooting
Connection
| Symptom | Cause and fix |
|---|---|
| Authentication rejected on Test Connection while the token is right | Zentail does not publish the header name. Switch Auth Header to Authorization: Bearer and test again. |
| Report endpoint failed on Test Report Access | The token was accepted by neither endpoint. Regenerate the token in Zentail and paste the new value; check for a trailing space. |
| Connection test raises a network error | The Odoo server cannot reach api.zentail.com over HTTPS. Check outbound firewall rules and the proxy configuration of the Odoo service. |
| Status returns to Draft after you save | The record was never tested successfully. A draft instance is skipped by every scheduled action. |
The requests library is required | requests is missing from Odoo's Python environment. Install it into the interpreter that runs Odoo. |
Reports and reads
| Symptom | Cause and fix |
|---|---|
| POST /v1/report did not return a report id | The account rejected the request body. Check that the first column header is SKU and that the CSV is comma-delimited. |
| Report … was still 'PENDING' after 300s | The report did not finish inside the 5-minute poll window. Export fewer columns or a narrower SKU set and run it again; very large exports should be split. |
| Report … completed without a filePath | Zentail returned a completed report with no file. Re-run the export; if it repeats, quote the report id in a support request. |
| report filePath is not a downloadable URL | The API returned something other than an https URL. Re-run; if it repeats, send us the report id. |
| HTTP 429 in the log | Rate limiting. Lengthen the scheduled-action interval, or run fewer manual operations at once; the client retries three times with backoff on 429/5xx before failing. |
| Inventory reads return data, but Odoo stock is unchanged | By design. Zentail publishes the inventory paths but not the response schema, so the payload is recorded on the log rather than written into stock. |
Webhooks
| Symptom | Cause and fix |
|---|---|
Invalid signature | The Client Secret on the instance does not match the secret Zentail signs with, or a proxy rewrote the body. Check the secret first. |
Instance not found | The X-Zentail-Shop-Domain header does not match any instance's Account Reference. |
| Webhooks verify but nothing happens | The Process Queue Jobs scheduled action is disabled, or Odoo's cron worker is not running. Check Settings → Technical → Scheduled Actions and that --max-cron-threads is greater than zero. |
| The Webhook Health Check starts flagging | Zentail has stopped delivering. Confirm the instance is reachable from the internet over HTTPS; a webhook to an unreachable host fails on Zentail's side, not here. |
Version history
| Odoo version | Version you install today |
|---|---|
| 17.0 | 17.0.2.0.2 |
| 18.0 | 18.0.2.0.2 |
| 19.0 | 19.0.2.0.2 |
ECOSIRE module versions are <odoo major>.<module major>.<minor>.<patch>, so 19.0.2.0.2 is the Odoo 19 build of module version 2.0.2. Your installed version is shown in Apps.
What changed in the 2.0.x line:
- 2.0.0 — the connector was rebuilt on Zentail's Open API: the report-based product flow, inventory / listing / order reads, the two connection tests and the Operations Center replaced the Shopify-shaped client the module had been cloned from.
- 2.0.1 / 2.0.2 — build numbering moved above the previously published release, and the configuration screens lost sixteen fields that were rendered but read by no code (order-workflow toggles, API version, pricelist and salesperson defaults, payout auto-reconcile flags, the refund "customer notified" flag, three webhook toggles and two carrier flags). Nothing that worked was removed: order automation stays on the Sale Auto Workflow model, where the flags are read.