Fejlesztői dokumentáció

Vezoryx Connect Developer Guide

Pontos bekötési útmutató API kulcsokhoz, HMAC aláíráshoz, outbox mintához, event payloadokhoz, idempotenciához és hibakereséshez.

This guide explains how to connect an external webshop, custom Flask shop, WooCommerce/Shopify bridge, or POS wrapper to Vezoryx.

Use this document when you need to send orders, payments, refunds, invoices, stock changes or POS sales into Vezoryx without guessing the API contract.

1. What Vezoryx Connect Does

Vezoryx Connect receives external business events and writes them into the correct tenant based on the integration credential.

Supported integration directions:

  • Custom Flask webshop
  • WooCommerce adapter wrapper
  • Shopify adapter wrapper
  • Generic POS/cash register wrapper
  • Custom external business system

Important rule: never send `tenant_id` or organization ownership from the webshop as authority. Vezoryx decides the tenant from the integration key.

2. Setup In Vezoryx Admin

  1. Log in as a tenant admin.
  2. Open `/admin/integrations`.
  3. Create a new integration.
  4. Choose the provider name:
  • `custom_flask` for a custom webshop
  • `woocommerce` for a WooCommerce wrapper
  • `shopify` for a Shopify wrapper
  • `pos_generic` for a POS/cash register wrapper
  1. Copy the one-time secret immediately.

You will receive:

  • `integration_key`: public integration id, for example `vx_int_...`
  • `integration_secret`: secret signing key, for example `vx_sec_...`

Store the secret only in the external system secret store or environment variables. Do not commit it and do not paste it into logs.

Recommended environment variables in the webshop:

VEZORYX_API_BASE_URL=https://www.vezoryx.com
VEZORYX_INTEGRATION_KEY=vx_int_...
VEZORYX_INTEGRATION_SECRET=vx_sec_...

3. Endpoints

Base API endpoints:

POST /api/integrations/events
GET  /api/integrations/products
POST /api/integrations/stock/reserve
POST /api/integrations/stock/release
POST /api/integrations/stock/decrease
GET  /api/integrations/sync/status

`GET /api/integrations/products?webshop_only=1&limit=200` returns sellable SKU rows with optional webshop grouping fields (`model_code`, color, size and barcode). Follow `next_cursor` with the `after_id` query parameter. `stock/reserve` atomically checks and deducts stock; insufficient stock returns HTTP 422. Use `stock/release` to compensate an expired or failed checkout.

Main event endpoint:

POST https://www.vezoryx.com/api/integrations/events

Every event request must be signed.

4. Authentication Headers

Required headers:

Content-Type: application/json
X-Vezoryx-Integration: vx_int_...
X-Vezoryx-Timestamp: 2026-06-01T10:00:00Z
X-Vezoryx-Signature: sha256=...
Idempotency-Key: order-10042-created

Signature message:

timestamp + "." + raw_json_body

Signature algorithm:

HMAC-SHA256(integration_secret, message)

Clock skew is limited. Generate `X-Vezoryx-Timestamp` at request time in UTC.

Do not call Vezoryx directly inside the checkout transaction. Use an outbox.

Recommended flow:

  1. Customer places an order.
  2. Webshop saves the order in its own database.
  3. In the same DB transaction, webshop saves a pending outbox event.
  4. A background worker sends pending outbox events to Vezoryx.
  5. If Vezoryx is temporarily unavailable, the outbox retries.
  6. The same `event_id` is reused for retries.

This prevents lost orders and protects checkout speed.

6. Python SDK Usage

Initialize the client:

import os

from vezoryx_connect import VezoryxClient

vezoryx = VezoryxClient(
    api_base_url=os.environ["VEZORYX_API_BASE_URL"],
    integration_key=os.environ["VEZORYX_INTEGRATION_KEY"],
    integration_secret=os.environ["VEZORYX_INTEGRATION_SECRET"],
)

Send a classic webshop order:

vezoryx.send_order_created(
    event_id=f"order-{order.id}-created",
    external_order_id=order.order_number,
    currency="HUF",
    customer={
        "external_customer_id": str(order.customer_id),
        "email": order.customer_email,
        "name": order.customer_name,
    },
    items=[
        {
            "sku": item.sku,
            "name": item.name,
            "quantity": item.quantity,
            "gross_unit_price": item.gross_price,
        }
        for item in order.items
    ],
)

Send a POS-style sale event from a custom webshop:

from vezoryx_connect.adapters import VezoryxCustomWebshopAdapter

adapter = VezoryxCustomWebshopAdapter(
    provider_location_id="webshop",
    provider_terminal_id="checkout",
)

event = adapter.order_created(
    {
        "external_order_id": order.order_number,
        "currency": "HUF",
        "gross_total": order.total_gross,
        "customer": {
            "email": order.customer_email,
            "name": order.customer_name,
        },
        "items": [
            {
                "sku": item.sku,
                "name": item.name,
                "quantity": item.quantity,
                "gross_unit_price": item.gross_price,
            }
            for item in order.items
        ],
    }
)

vezoryx.send_raw_event(event)

7. Outbox Pattern

Create a table in the webshop database:

CREATE TABLE integration_outbox (
    id INTEGER PRIMARY KEY,
    event_id TEXT UNIQUE NOT NULL,
    event_type TEXT NOT NULL,
    payload TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    retry_count INTEGER NOT NULL DEFAULT 0,
    last_error TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    sent_at TIMESTAMP
);

SQLAlchemy-style model example:

class IntegrationOutbox(db.Model):
    __tablename__ = "integration_outbox"

    id = db.Column(db.Integer, primary_key=True)
    event_id = db.Column(db.String(255), unique=True, nullable=False)
    event_type = db.Column(db.String(120), nullable=False)
    payload = db.Column(db.JSON, nullable=False)
    status = db.Column(db.String(30), nullable=False, default="pending")
    retry_count = db.Column(db.Integer, nullable=False, default=0)
    last_error = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    sent_at = db.Column(db.DateTime)

Enqueue after order creation:

from vezoryx_connect.outbox import enqueue_event

enqueue_event(
    db.session,
    IntegrationOutbox,
    "order.created",
    f"order-{order.id}-created",
    {
        "external_order_id": order.order_number,
        "currency": "HUF",
        "customer": {
            "email": order.customer_email,
            "name": order.customer_name,
        },
        "items": [
            {
                "sku": item.sku,
                "name": item.name,
                "quantity": item.quantity,
                "gross_unit_price": item.gross_price,
            }
            for item in order.items
        ],
    },
)
db.session.commit()

Process pending events from a scheduled worker:

from vezoryx_connect.outbox import process_pending_outbox

result = process_pending_outbox(
    db.session,
    IntegrationOutbox,
    vezoryx,
    limit=50,
    max_attempts=5,
)

print(result.sent, result.failed)

8. Event Types

General webshop events:

  • `product.created`
  • `product.updated`
  • `product.deleted`
  • `customer.created`
  • `customer.updated`
  • `order.created`
  • `order.paid`
  • `order.cancelled`
  • `order.refunded`
  • `order.fulfilled`
  • `inventory.reserved`
  • `inventory.released`
  • `inventory.decreased`
  • `inventory.adjusted`

POS/canonical commerce events:

  • `sale.created`
  • `payment.created`
  • `refund.created`
  • `daily_closing.created`
  • `invoice.created`

For new integrations, prefer the POS/canonical commerce events when the source system has separate order/payment/refund/invoice events.

9. `order.created` Payload

Required:

  • `event_id`
  • `event_type`
  • `external_order_id`
  • `currency`
  • `items[]`

Recommended:

  • `occurred_at`
  • `source`
  • `customer`

Example:

{
  "event_type": "order.created",
  "event_id": "order-10042-created",
  "occurred_at": "2026-06-01T10:00:00Z",
  "source": "custom_flask_webshop",
  "external_order_id": "ORDER-10042",
  "currency": "HUF",
  "customer": {
    "external_customer_id": "CUST-582",
    "email": "customer@example.com",
    "name": "Teszt Vasarlo"
  },
  "items": [
    {
      "sku": "SKU-1",
      "name": "Termek",
      "quantity": 2,
      "gross_unit_price": 4500
    }
  ]
}

Result in Vezoryx:

  • creates or updates partner from `customer`
  • creates an internal order
  • maps `external_order_id` to the internal order
  • creates order items
  • calculates total amount
  • stores the event in `integration_events`

10. Canonical POS Payload

POS/canonical events use a stricter schema.

Required common fields:

  • `event_id`
  • `event_type`
  • `provider`
  • `provider_location_id`
  • `provider_terminal_id`
  • `occurred_at`
  • `currency`
  • `gross_total`

Recommended common fields:

  • `net_total`
  • `vat_total`
  • `metadata`

`sale.created` requires:

  • `external_sale_id`
  • `items[]`

`payment.created` requires:

  • `external_sale_id`
  • `external_payment_id`

`refund.created` requires:

  • `external_sale_id`
  • `external_refund_id`

`invoice.created` requires:

  • `external_sale_id`
  • `external_invoice_id`

`daily_closing.created` requires:

  • `external_closing_id`
  • `business_date`

Example `sale.created`:

{
  "event_type": "sale.created",
  "event_id": "pos-sale-SALE-100",
  "occurred_at": "2026-06-01T10:00:00Z",
  "source": "pos_generic",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_sale_id": "SALE-100",
  "external_order_id": "SALE-100",
  "receipt_number": "R-100",
  "currency": "HUF",
  "gross_total": 9000,
  "net_total": 7086.61,
  "vat_total": 1913.39,
  "customer": {
    "email": "buyer@example.com",
    "name": "Buyer Name"
  },
  "items": [
    {
      "sku": "SKU-1",
      "name": "Termek",
      "quantity": 2,
      "gross_unit_price": 4500,
      "vat_rate": 27
    }
  ],
  "payments": [
    {
      "external_payment_id": "PAY-100",
      "method": "card",
      "amount": 9000,
      "currency": "HUF"
    }
  ],
  "metadata": {
    "cashier_id": "CASHIER-1"
  }
}

Example `payment.created`:

{
  "event_type": "payment.created",
  "event_id": "pos-payment-PAY-100",
  "occurred_at": "2026-06-01T10:01:00Z",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_sale_id": "SALE-100",
  "external_payment_id": "PAY-100",
  "currency": "HUF",
  "gross_total": 9000,
  "method": "card"
}

Example `refund.created`:

{
  "event_type": "refund.created",
  "event_id": "pos-refund-REF-100",
  "occurred_at": "2026-06-01T10:10:00Z",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_sale_id": "SALE-100",
  "external_refund_id": "REF-100",
  "currency": "HUF",
  "gross_total": 9000,
  "reason": "customer_return"
}

Example `daily_closing.created`:

{
  "event_type": "daily_closing.created",
  "event_id": "pos-closing-Z-20260601-1",
  "occurred_at": "2026-06-01T22:00:00Z",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_closing_id": "Z-20260601-1",
  "business_date": "2026-06-01",
  "currency": "HUF",
  "gross_total": 30000,
  "payment_totals": {
    "card": 22000,
    "cash": 8000
  },
  "tax_totals": {
    "27": 6377.95
  },
  "sales": [
    {"external_sale_id": "SALE-100", "gross_total": 9000},
    {"external_sale_id": "SALE-101", "gross_total": 21000}
  ]
}

If `sales[]` is provided, Vezoryx validates that the sales gross total matches `gross_total`.

11. Idempotency Rules

Every event must have:

  • stable `event_id`
  • `Idempotency-Key` header

The SDK automatically sets `Idempotency-Key` to `event_id`.

For POS/canonical events, Vezoryx also protects against duplicates by:

  • same `event_id`
  • same `event_type` + external reference

Examples:

  • same `sale.created` + same `external_sale_id` does not create a second order
  • same `refund.created` + same `external_refund_id` does not run refund logic again
  • retrying the same request returns `duplicate: true` after it was processed

Use deterministic event IDs:

order-{order_id}-created
payment-{payment_id}-created
refund-{refund_id}-created
invoice-{invoice_id}-created
closing-{business_date}-{terminal_id}

Do not use random UUIDs for retries of the same business event.

12. Manual HMAC Request Without SDK

Use this if the external system is not Python.

Python reference implementation:

import hashlib
import hmac
import json
from datetime import datetime, timezone
from urllib.request import Request, urlopen

api_base_url = "https://www.vezoryx.com"
integration_key = "vx_int_..."
integration_secret = "vx_sec_..."

event = {
    "event_type": "order.created",
    "event_id": "order-10042-created",
    "external_order_id": "ORDER-10042",
    "currency": "HUF",
    "items": [{"sku": "SKU-1", "quantity": 1, "gross_unit_price": 4500}],
}

body = json.dumps(event, separators=(",", ":"), sort_keys=True).encode("utf-8")
timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
message = timestamp.encode("utf-8") + b"." + body
signature = hmac.new(integration_secret.encode("utf-8"), message, hashlib.sha256).hexdigest()

request = Request(
    f"{api_base_url}/api/integrations/events",
    data=body,
    headers={
        "Content-Type": "application/json",
        "X-Vezoryx-Integration": integration_key,
        "X-Vezoryx-Timestamp": timestamp,
        "X-Vezoryx-Signature": f"sha256={signature}",
        "Idempotency-Key": event["event_id"],
    },
    method="POST",
)

with urlopen(request, timeout=10) as response:
    print(response.read().decode("utf-8"))

Successful response:

{
  "received": true,
  "duplicate": false,
  "event_id": "order-10042-created",
  "result": {
    "order_id": 123
  }
}

Duplicate response:

{
  "received": true,
  "duplicate": true,
  "event_id": "order-10042-created"
}

13. Product And Stock Sync

Fetch Vezoryx products:

GET /api/integrations/products

Decrease stock:

vezoryx.decrease_inventory(
    event_id="stock-order-10042-item-1",
    sku="SKU-1",
    quantity=1,
)

Raw event equivalent:

{
  "event_type": "inventory.decreased",
  "event_id": "stock-order-10042-item-1",
  "sku": "SKU-1",
  "quantity": 1
}

Use stock events only after deciding which system is the inventory authority. If the webshop is not the source of truth for stock, send sales/orders and let Vezoryx handle stock changes internally.

14. WooCommerce Wrapper

The WooCommerce adapter maps WooCommerce REST/webhook payloads to Vezoryx events.

from vezoryx_connect import VezoryxClient
from vezoryx_connect.adapters import woocommerce_order_to_event

event = woocommerce_order_to_event(woocommerce_order, event_type="order.created")
vezoryx.send_raw_event(event)

Recommended WooCommerce wrapper flow:

  1. Receive WooCommerce webhook.
  2. Verify WooCommerce webhook authenticity in your wrapper.
  3. Convert payload with `woocommerce_order_to_event`.
  4. Send to Vezoryx with `send_raw_event`.
  5. Return `200` to WooCommerce only after enqueueing or sending.

15. Shopify Wrapper

Verify Shopify HMAC before forwarding the event.

from vezoryx_connect.adapters import shopify_order_to_event, verify_shopify_hmac

if not verify_shopify_hmac(raw_body, request.headers["X-Shopify-Hmac-Sha256"], shopify_shared_secret):
    abort(401)

event = shopify_order_to_event(shopify_order, event_type="order.created")
vezoryx.send_raw_event(event)

16. Mock POS Adapter

Use `MockPOSAdapter` for local demos, automated tests and UI development before a real POS provider is available.

from vezoryx_connect.adapters import MockPOSAdapter

mock_pos = MockPOSAdapter(
    provider_location_id="STORE-1",
    provider_terminal_id="TERM-1",
)

event = mock_pos.create_sale(
    id="SALE-100",
    currency="HUF",
    items=[
        {"sku": "SKU-1", "quantity": 1, "gross_unit_price": 4500},
    ],
)

vezoryx.send_raw_event(event)

The mock adapter uses the same event contract as future Laurel, Micra, HioPOS or custom POS adapters.

17. Status And Debugging

Check integration status:

GET /api/integrations/sync/status

Debug tables in Vezoryx:

  • `integration_events`: all Connect API events
  • `pos_events`: canonical POS events with raw and normalized payloads

When a customer reports an issue, check:

  1. Did the event arrive?
  2. Was the HMAC valid?
  3. Was the payload accepted or rejected?
  4. Is the event marked `processed`, `failed`, or duplicate?
  5. What is stored in `raw_payload`?
  6. What is stored in `normalized_payload`?
  7. Did the event map to an internal order?

18. Error Responses

Common responses:

400 unsupported event_type
400 event_id is required
400 Idempotency-Key header is required
400 invalid json payload
400 currency must be a 3-letter ISO code
400 items[0].quantity must not be negative
401 missing authentication headers
401 integration not found or inactive
401 invalid timestamp
401 stale timestamp
401 invalid signature
413 payload too large
422 refund references an unknown external sale
500 event processing failed

Retry policy:

  • Retry network errors.
  • Retry `500`.
  • Do not retry `400` until the payload is fixed.
  • Do not retry `401` until credentials/signature are fixed.
  • Review `422` manually because it usually means business data is missing or out of order.

19. Developer Checklist

Before going live:

  • Integration created in `/admin/integrations`
  • Secret stored outside code
  • Webshop uses deterministic `event_id`
  • `Idempotency-Key` is sent
  • Outbox table exists
  • Checkout saves order and outbox event in one transaction
  • Background worker processes pending outbox events
  • Failed outbox events are visible to operators
  • Product SKUs match between webshop and Vezoryx
  • One test order creates one internal Vezoryx order
  • Retrying the same event does not create a duplicate order
  • Payment event maps to the existing sale/order
  • Refund event maps to the existing sale/order
  • Invalid payload is rejected and logged
  • `integration_events` and `pos_events` contain useful debug data

20. Minimal End-To-End Test

  1. Create a test integration in Vezoryx.
  2. Add credentials to the webshop `.env`.
  3. Create one test product in Vezoryx with SKU `SKU-1`.
  4. Send one `order.created` or `sale.created`.
  5. Confirm a Vezoryx order was created.
  6. Send the exact same event again.
  7. Confirm the response has `duplicate: true`.
  8. Confirm there is still only one internal order.
  9. Send a payment event for the same external order/sale.
  10. Confirm the event is logged as processed.

21. Security Notes

  • Use HTTPS only.
  • Rotate the integration secret if it is exposed.
  • Never log `integration_secret`.
  • Do not trust tenant IDs from external payloads.
  • Keep payload size under the configured API limit.
  • Use outbox retries instead of synchronous checkout dependency.
  • Keep POS provider credentials separate from Vezoryx credentials.