Skip to content

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.placed and order.status_changed (priority 70). A new_order export is enqueued for order.placed; a status_update export for order.status_changed (only when the new status is in the configured trigger_statuses). Each enqueue immediately kicks the export workflow, and the durable queue row survives if that kick fails.
  • Temporal workflow + activityOpenshipExportWorkflow runs the process_openship_exports activity, 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-drain runs 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 via python -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

  1. 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-drain schedule is created together with the core schedules by python -m vectis.schedules.
  2. 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.
  3. Configure routing: set trigger_statuses, state_rules, default routing values, price adjustment, and the customer-match key.
  4. Optionally seed ext_openship_localization from your legacy store's mapping table with the seed_from_woo module (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 enabled is falsy, order events are ignored and the drain returns {"disabled": true}.
  • New orders are once-per-order; status updates stackenqueue_export skips a new_order row if one already exists for the order, while each status transition is its own status_update push.
  • Retries and caps — an export retries up to MAX_ATTEMPTS (8) across the scheduled drain and event kicks. A missing order is marked failed; 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_rules entry; unmatched fields fall back to the default_* 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.