Webhooks

Receive real-time HTTPS callbacks when SalesVu commerce events occur. Verify signatures, read the event envelope, then fetch full detail via data.api_url when needed.

Sandbox API 2026-07-17 HMAC-SHA256
1

Overview

How SalesVu delivers webhook events to your HTTPS endpoint.

  1. You register an HTTPS endpoint and subscribe to event types in the Developer Portal.
  2. When a subscribed event occurs, SalesVu queues a delivery and POSTs JSON to your URL.
  3. You verify X-SalesVu-Signature, process type and data.object, then return HTTP 2xx.
  4. Most events use summary payloads — use data.api_url (REST v1) for full resources.

POS Sync sources (order.created, customer.created, customer.updated): Sync only stages eligible events after a cached eligibility check (active endpoint + subscription + partner scopes). A server worker then emits into the outbox; the delivery worker performs HTTP. Cart/CRM and other sources still emit directly into the outbox.

Subscribe to the events you need. Each event lists a recommended data scope (e.g. read:orders). Apps typically also request webhook:notifications.

2

Configure an endpoint

One HTTPS URL per OAuth app (Phase 1), with subscribed events and a signing secret.

  1. Open your app → Webhooks.
  2. Enter an HTTPS endpoint URL and select event types.
  3. Save — copy the signing secret (whsec_…) immediately; it is shown once.
  4. Use Send test event now to verify connectivity (webhook.test).

You can rotate the signing secret later; update your receiver before rotating in production.

Delivery history (send status + receiver outcome when using the sample endpoint) appears under Recent deliveries.

3

Event types

Subscribe only to events your integration needs. Payload mode is usually summary.

Event Description Mode Data scope Domain
webhook.test Test webhook full webhook:notifications system
order.created Order created summary read:orders orders
order.updated Order updated summary read:orders orders
order.refunded Order refunded summary read:orders orders
order.deleted Order deleted summary read:orders orders
inventory.updated Inventory updated summary read:inventory inventory
catalog.product.created Catalog product created summary read:catalog catalog
catalog.product.updated Catalog product updated summary read:catalog catalog
catalog.product.deleted Catalog product deleted summary read:catalog catalog
customer.created Customer created summary read:customers customers
customer.updated Customer updated summary read:customers customers
purchase_order.created Purchase order created summary read:purchase_orders purchase_orders
purchase_order.updated Purchase order updated summary read:purchase_orders purchase_orders

Tip: Jump to Examples for a full JSON body per event. For summary events, call data.api_url with your OAuth access token for the full resource.

4

Event envelope

Every POST body is a JSON object with a stable top-level shape.

FieldDescription
idEvent id (evt_…), also sent as X-SalesVu-Event-Id.
typeEvent type string (e.g. order.created).
api_versionPlatform API version for this payload.
created_atUTC creation time (ISO-8601).
owner_idMerchant owner id when applicable (null for webhook.test).
store_idLocation / store id when applicable (null for webhook.test).
data.objectEvent-specific summary object (see Examples).
data.api_urlOptional absolute REST v1 URL hint (path only — no query string). Call with OAuth token using the method/body below.

Verified data.api_url targets (match emit paths in WebhookManager and routes in api/api-server/v1/routes.php):

Events Method URL (under API v1 base) Request body
order.created, order.updated, order.refunded, order.deleted POST https://api-dev.salesvu.com/v1/orders/get-store-order location_id (int), order_id (int), payment_device (string — use data.object.pos_id)
catalog.product.created, catalog.product.updated, catalog.product.deleted POST https://api-dev.salesvu.com/v1/catalog/products location_id (int), optional product_id (int)
customer.created, customer.updated POST https://api-dev.salesvu.com/v1/customers/get location_id or owner_id (int, one required), customer_ids (array of string GUIDs — wrap data.object.customer_id)
purchase_order.created, purchase_order.updated POST https://api-dev.salesvu.com/v1/purchase-orders/get location_id (int), purchase_order_id (int)
inventory.updated POST https://api-dev.salesvu.com/v1/inventory/list location_id (int), optional product_id (int)

Illustrative values in Examples use sample ids only — production payloads use real merchant data. Map store_id from the webhook to API location_id.

5

Examples by event

Complete sample POST bodies for each registered webhook type.

webhook.test — Test webhook

Sent from Developer Portal → Send test event now. Payload mode is full; owner_id / store_id may be null.

Mode: full · Scope: webhook:notifications · Domain: system

{
    "id": "evt_01EXAMPLE",
    "type": "webhook.test",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": null,
    "store_id": null,
    "data": {
        "object": {
            "ok": true,
            "message": "SalesVu webhook test event",
            "subscriber_type": "oauth_app",
            "subscriber_id": "app_01EXAMPLE"
        }
    }
}

order.created — Order created

Summary ids at queue time; delivery may expand order detail. Refetch: POST data.api_url with location_id, order_id, payment_device (= pos_id). Payment/card are not embedded.

Mode: summary · Scope: read:orders · Domain: orders

{
    "id": "evt_01EXAMPLE",
    "type": "order.created",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "order_id": 5001,
            "pos_id": "SalesVu.com"
        },
        "api_url": "https://api-dev.salesvu.com/v1/orders/get-store-order"
    }
}

order.updated — Order updated

Registered event; same summary / expand shape as order.created. Refetch: POST data.api_url with location_id, order_id, payment_device (= pos_id).

Mode: summary · Scope: read:orders · Domain: orders

{
    "id": "evt_01EXAMPLE",
    "type": "order.updated",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "order_id": 5001,
            "pos_id": "SalesVu.com"
        },
        "api_url": "https://api-dev.salesvu.com/v1/orders/get-store-order"
    }
}

order.refunded — Order refunded

Original order ids plus optional refund fields. Refetch original order: POST data.api_url with location_id, order_id, payment_device (= pos_id).

Mode: summary · Scope: read:orders · Domain: orders

{
    "id": "evt_01EXAMPLE",
    "type": "order.refunded",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "order_id": 5001,
            "pos_id": "SalesVu.com",
            "refund_id": 7001,
            "refund_order_id": 5002,
            "refund_pos_id": "SalesVu.com",
            "amount": 9.99,
            "refund_type": "full",
            "full_refund": 1
        },
        "api_url": "https://api-dev.salesvu.com/v1/orders/get-store-order"
    }
}

order.deleted — Order deleted

Summary-only (no expand). Soft delete sets deleted=1 on the order row. Permanent delete also sets deleted_permanently=1; get-store-order may 404 after hard delete. Optional deleted_by_support=1 when a support user deletes via Orders Report.

Mode: summary · Scope: read:orders · Domain: orders

Soft delete (Orders Report Delete):

{
    "id": "evt_01EXAMPLE",
    "type": "order.deleted",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "order_id": 5001,
            "pos_id": "SalesVu.com",
            "deleted": 1,
            "status": "deleted",
            "source": "orders_report_soft_delete"
        },
        "api_url": "https://api-dev.salesvu.com/v1/orders/get-store-order"
    }
}

Permanent delete (support Delete Permanently — includes deleted_permanently):

{
    "id": "evt_01EXAMPLE",
    "type": "order.deleted",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "order_id": 5001,
            "pos_id": "SalesVu.com",
            "deleted": 1,
            "status": "deleted",
            "deleted_permanently": 1,
            "source": "orders_report_permanent_delete"
        },
        "api_url": "https://api-dev.salesvu.com/v1/orders/get-store-order"
    }
}

inventory.updated — Inventory updated

Emitted when stock changes (orders, PO receive, transfers, adjustments, count sheets, RTV, etc.). Not emitted for first inventory row insert at qty 0. Refetch: POST data.api_url with location_id and optional product_id.

Mode: summary · Scope: read:inventory · Domain: inventory

{
    "id": "evt_01EXAMPLE",
    "type": "inventory.updated",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "location_id": 67890,
            "product_id": 1001,
            "unit_id": 1,
            "quantity": 42,
            "required_quantity": 0,
            "available_quantity": 42,
            "threshold_quantity": 5,
            "product_name": "Example Product",
            "sku": "SKU-1001",
            "barcode": "012345678905",
            "source": "order_deduction"
        },
        "api_url": "https://api-dev.salesvu.com/v1/inventory/list"
    }
}

catalog.product.created — Catalog product created

Summary product fields only. Refetch: POST data.api_url with location_id and optional product_id.

Mode: summary · Scope: read:catalog · Domain: catalog

{
    "id": "evt_01EXAMPLE",
    "type": "catalog.product.created",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "product_id": 1001,
            "product_name": "Example Product",
            "product_short_name": "Example",
            "description": "Sample description",
            "sku": "SKU-1001",
            "barcode": "012345678905",
            "selling_price": 9.99,
            "ecommerce_price": 9.99,
            "category_id": 10,
            "top_category_id": 1,
            "category_name": "Beverages",
            "unit_id": 1,
            "parent_product_id": 0,
            "for_sale": 1,
            "product_type": 0,
            "is_active": 1,
            "status": "active",
            "online_status": 1,
            "image": "",
            "deleted": 0
        },
        "api_url": "https://api-dev.salesvu.com/v1/catalog/products"
    }
}

catalog.product.updated — Catalog product updated

Same summary shape as catalog.product.created. Refetch: POST data.api_url with location_id and optional product_id.

Mode: summary · Scope: read:catalog · Domain: catalog

{
    "id": "evt_01EXAMPLE",
    "type": "catalog.product.updated",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "product_id": 1001,
            "product_name": "Example Product",
            "product_short_name": "Example",
            "description": "Sample description",
            "sku": "SKU-1001",
            "barcode": "012345678905",
            "selling_price": 9.99,
            "ecommerce_price": 9.99,
            "category_id": 10,
            "top_category_id": 1,
            "category_name": "Beverages",
            "unit_id": 1,
            "parent_product_id": 0,
            "for_sale": 1,
            "product_type": 0,
            "is_active": 1,
            "status": "active",
            "online_status": 1,
            "image": "",
            "deleted": 0
        },
        "api_url": "https://api-dev.salesvu.com/v1/catalog/products"
    }
}

catalog.product.deleted — Catalog product deleted

Includes soft-deleted product; deleted=1 and status=deleted. Refetch: POST data.api_url with location_id and optional product_id.

Mode: summary · Scope: read:catalog · Domain: catalog

{
    "id": "evt_01EXAMPLE",
    "type": "catalog.product.deleted",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "product_id": 1001,
            "product_name": "Example Product",
            "product_short_name": "Example",
            "description": "Sample description",
            "sku": "SKU-1001",
            "barcode": "012345678905",
            "selling_price": 9.99,
            "ecommerce_price": 9.99,
            "category_id": 10,
            "top_category_id": 1,
            "category_name": "Beverages",
            "unit_id": 1,
            "parent_product_id": 0,
            "for_sale": 1,
            "product_type": 0,
            "is_active": 0,
            "status": "deleted",
            "online_status": 1,
            "image": "",
            "deleted": 1
        },
        "api_url": "https://api-dev.salesvu.com/v1/catalog/products"
    }
}

customer.created — Customer created

customer_id is a string GUID (never cast to int). Refetch: POST data.api_url with customer_ids: [customer_id] and location_id or owner_id.

Mode: summary · Scope: read:customers · Domain: customers

{
    "id": "evt_01EXAMPLE",
    "type": "customer.created",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "customer_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "owner_id": 12345,
            "store_id": 67890,
            "first_name": "Jane",
            "middle_name": "",
            "last_name": "Doe",
            "email": "[email protected]",
            "phone": "+15551234567",
            "address": "123 Main St",
            "city": "Austin",
            "state": "TX",
            "country": "US",
            "zipcode": "78701",
            "account_number": "CUST-1001",
            "barcode": "",
            "external_customer_id": "",
            "source": "pos"
        },
        "api_url": "https://api-dev.salesvu.com/v1/customers/get"
    }
}

customer.updated — Customer updated

Same summary shape as customer.created. Refetch: POST data.api_url with customer_ids: [customer_id] and location_id or owner_id.

Mode: summary · Scope: read:customers · Domain: customers

{
    "id": "evt_01EXAMPLE",
    "type": "customer.updated",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "customer_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "owner_id": 12345,
            "store_id": 67890,
            "first_name": "Jane",
            "middle_name": "",
            "last_name": "Doe",
            "email": "[email protected]",
            "phone": "+15551234567",
            "address": "123 Main St",
            "city": "Austin",
            "state": "TX",
            "country": "US",
            "zipcode": "78701",
            "account_number": "CUST-1001",
            "barcode": "",
            "external_customer_id": "",
            "source": "pos"
        },
        "api_url": "https://api-dev.salesvu.com/v1/customers/get"
    }
}

purchase_order.created — Purchase order created

PO header summary. Refetch: POST data.api_url with location_id and purchase_order_id.

Mode: summary · Scope: read:purchase_orders · Domain: purchase_orders

{
    "id": "evt_01EXAMPLE",
    "type": "purchase_order.created",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "purchase_order_id": 9001,
            "vendor_id": 55,
            "po_name": "PO-9001",
            "status": 1,
            "payment_status": 0,
            "amount": 250,
            "discount_amount": 0,
            "total_charges": 250,
            "external_order_id": "",
            "vendor_invoice_number": "INV-55",
            "parent_purchase_order_id": 0,
            "expected_delivery_date": "2026-08-30",
            "receive_date": "",
            "create_date": "2026-08-24 10:00:00",
            "update_date": "2026-08-24 11:00:00",
            "deleted": 0,
            "approved_by": 0,
            "vendor_approval_status": 0
        },
        "api_url": "https://api-dev.salesvu.com/v1/purchase-orders/get"
    }
}

purchase_order.updated — Purchase order updated

Same header summary as purchase_order.created. Refetch: POST data.api_url with location_id and purchase_order_id.

Mode: summary · Scope: read:purchase_orders · Domain: purchase_orders

{
    "id": "evt_01EXAMPLE",
    "type": "purchase_order.updated",
    "api_version": "2026-07-17",
    "created_at": "2026-08-24T12:00:00+00:00",
    "owner_id": 12345,
    "store_id": 67890,
    "data": {
        "object": {
            "store_id": 67890,
            "purchase_order_id": 9001,
            "vendor_id": 55,
            "po_name": "PO-9001",
            "status": 1,
            "payment_status": 0,
            "amount": 250,
            "discount_amount": 0,
            "total_charges": 250,
            "external_order_id": "",
            "vendor_invoice_number": "INV-55",
            "parent_purchase_order_id": 0,
            "expected_delivery_date": "2026-08-30",
            "receive_date": "",
            "create_date": "2026-08-24 10:00:00",
            "update_date": "2026-08-24 11:00:00",
            "deleted": 0,
            "approved_by": 0,
            "vendor_approval_status": 0
        },
        "api_url": "https://api-dev.salesvu.com/v1/purchase-orders/get"
    }
}
6

Headers & signature verification

Always verify authenticity before trusting the body.

Request method: POST · Content-Type: application/json

HeaderDescription
X-SalesVu-Event-IdSame as envelope id.
X-SalesVu-Event-TypeSame as envelope type.
X-SalesVu-Delivery-IdDelivery attempt id (dlv_…) for idempotency / logs.
X-SalesVu-Signaturet={unix_seconds},v1={hex_hmac}

Verify (required):

  1. Read the raw request body bytes (do not re-encode JSON before verify).
  2. Parse t and v1 from X-SalesVu-Signature.
  3. Reject if |now - t| is greater than your tolerance (e.g. 300 seconds).
  4. Compute HMAC-SHA256(signing_secret, t + "." + raw_body) as lowercase hex.
  5. Compare to v1 with a constant-time compare.

Pseudocode:

signed_payload = t + "." + raw_body
expected_v1    = hex(HMAC_SHA256(signing_secret, signed_payload))
accept if timing_safe_equal(expected_v1, v1)
7

Respond & retries

Acknowledge quickly; process asynchronously when needed.

  • Return HTTP 2xx when the event was accepted (signature OK and you queued/stored it).
  • Return 4xx for permanent client errors (bad signature / invalid payload) when appropriate.
  • Non-2xx responses may be retried by SalesVu. Make handlers idempotent using id or X-SalesVu-Delivery-Id.
  • Keep your handler fast; do heavy work after acknowledging.

Use Replay on a delivery in the Developer Portal Webhooks page to re-queue a send for testing.