OpenShip Export¶
The ext_openship extension exports orders to OpenShip, a fulfillment
orchestrator. Order events enqueue a durable, extension-owned export row, and a
Temporal workflow drains the queue by POSTing to OpenShip's webhooks — for both
new orders and status updates. The export payload carries state-based routing
rules, address localization, and a price-adjustment percentage. Whatever
OpenShip does downstream (Odoo, warehouses) is its concern; this extension only
speaks webhooks. Use it when OpenShip is your fulfillment router.
At a glance
| Extension id | openship |
| Category | fulfillment |
| Version | 0.1.0 |
| Provides | Order-event subscriptions (2), Temporal workflow + activity, scheduled retry drain, export/localization models, migrations |
| External account | OpenShip webhook endpoints (optional bearer token) |
What It Provides¶
- Order-event subscriptions — on activation the extension subscribes to
order.placedandorder.status_changed(priority 70). Anew_orderexport is enqueued fororder.placed; astatus_updateexport fororder.status_changed(only when the new status is in the configuredtrigger_statuses). Each enqueue immediately kicks the export workflow, and the durable queue row survives if that kick fails. - Temporal workflow + activity —
OpenshipExportWorkflowruns theprocess_openship_exportsactivity, which sends pending/failed exports and returns sent/failed/skipped counts. The workflow retries up to 3 attempts (10s initial, 5m max interval); the activity is idempotent, so overlapping runs are harmless. - Scheduled retry drain — a Temporal schedule
vectis-openship-export-drainruns the workflow every 10 minutes to retry pending/failed exports (attempts capped at 8). - Export + localization models — the extension owns two tables (registered
via the manifest's
models()hook, created by its own migrations):ext_openship_exports— one row per outbound push (new order or status update) with retry accounting, last error, sent payload, and sent-at.ext_openship_localization— source country/state → fulfillment-side codes, applied to order addresses in the export payload. Seedable from a legacy WooCommerce table viapython -m vectis.extensions.ext_openship.seed_from_woo <mysql-dsn>.
- Rich new-order payload — order totals and lines, localized billing and shipping addresses, state-based routing (company id / customer class / sales channel), a price-adjustment factor, and a customer-match key.
The workflow, activity, and schedule are declared on the extension via the self-registration hooks, so the Temporal worker picks them up without core edits.
Setup¶
- Once installed (or dev-linked in development), the extension is discovered at startup; per-channel enablement is
managed under Settings > Extensions. Its migrations create the export
and localization tables. The Temporal worker must be running, and the
vectis-openship-export-drainschedule is created together with the core schedules bypython -m vectis.schedules. - Set the extension's settings (the
ext_openship.*keys below): enable it and provide the new-order and status-update webhook URLs, plus an optional bearer token. - Configure routing: set
trigger_statuses,state_rules, default routing values, price adjustment, and the customer-match key. - Optionally seed
ext_openship_localizationfrom your legacy store's mapping table with theseed_from_woomodule (idempotent — it truncates and re-fills the table).
Configuration Reference¶
The manifest declares no config_schema. Settings are stored as global
(channel-less) Setting rows under the ext_openship. prefix:
| Key | Type | Default | Description |
|---|---|---|---|
ext_openship.enabled |
boolean string | "false" |
Master on/off; accepts 1/true/yes |
ext_openship.new_order_url |
string | "" |
Webhook URL for new-order pushes |
ext_openship.status_update_url |
string | "" |
Webhook URL for status-update pushes |
ext_openship.api_key |
string | "" |
Optional bearer token sent on both webhooks |
ext_openship.trigger_statuses |
JSON list | ["processing", "awaiting_payment"] |
Order statuses that trigger an export |
ext_openship.state_rules |
JSON list | [] |
Routing rules {state, company_id, customer_class, sales_channel} |
ext_openship.default_company_id |
string | "" |
Company id fallback when no state rule matches |
ext_openship.default_customer_class |
string | "" |
Customer-class fallback |
ext_openship.default_sales_channel |
string | "web" |
Sales-channel fallback |
ext_openship.price_adjustment_percent |
string | "0" |
Percent → factor in the payload (e.g. "10" → 0.10) |
ext_openship.match_customer_by |
string | "billing_email" |
account_email or billing_email |
Operational Notes¶
- Disabled = no export — when
enabledis falsy, order events are ignored and the drain returns{"disabled": true}. - New orders are once-per-order; status updates stack —
enqueue_exportskips anew_orderrow if one already exists for the order, while each status transition is its ownstatus_updatepush. - Retries and caps — an export retries up to
MAX_ATTEMPTS(8) across the scheduled drain and event kicks. A missing order is markedfailed; a missing webhook URL is skipped (not counted against attempts). Errors are stored (last_error, truncated to 2000 chars) and logged. - Address localization — for each address the country/state is remapped via
ext_openship_localization: a matching state row wins, then a country-only row, then the source values pass through. - State-based routing — the shipping state (falling back to billing state)
selects a
state_rulesentry; unmatched fields fall back to thedefault_*settings. - Status-update payload is lean — status updates send only order number, status, the customer-match key, and email; only the new-order push carries the full localized payload.
Source: ext_openship/extension.py — in the enterprise-extensions repo.