Transactions integration guide
Transactions Integration Guide
This guide explains the Transaction object in Unidy: what it represents, how it is structured, how it relates to users and other Unidy objects, and how brands integrate with it through Unidy's APIs and webhooks.
Detailed request / response schemas and the full list of query parameters live in the Swagger API docs (V1 REST API, SDK API). This guide focuses on concepts, structure, and integration flows.
1. Overview
A Transaction is a financial record (an order, a purchase, a refund) that belongs to a single User and is grouped under a Transaction Category chosen by the brand. Every transaction carries information about its origin (the source platform and the platform's own ID) so the same record can be reconciled across systems.
Transactions are richer than Tickets or Subscriptions: they carry a full money breakdown (subtotal, tax, shipping, discounts, paid and refunded amounts), payment metadata, billing and shipping addresses, and one or more line items describing what was actually purchased.
Transaction vs Ticket vs Subscription
Concept | Use it for | What makes it distinct |
Transaction | One-off orders, purchases, refunds | Money breakdown, line items, billing & shipping addresses |
Ticket | Event admission, passes, bookings | Validity window ( starts_at / ends_at), venue, single price |
Subscription | Recurring entitlements (memberships, plans) | Recurring billing window, plan reference |
If the customer paid once for goods or a one-time service, model it as a Transaction. If they received a time-bound admission, model it as a Ticket. If they receive an ongoing entitlement, model it as a Subscription.
Where transactions come from
Transactions enter Unidy via REST API. Transactions can also be created or corrected from the Dashboard, but this is intended for occasional admin / support work — not as a regular ingestion mechanism.
2. The Transaction object
A Transaction is a single record with the following groups of information. Field names below match the JSON keys returned by the API and webhook payloads.
Attributes marked with a star (*) in the tables below are mandatory.
Identity
Field | Description |
id | Unidy's public ID for the transaction (UUID). Use this in API URLs and to correlate webhook events. Assigned by Unidy on create. |
external_id* | Your identifier from the source platform (e.g. a Shopify order ID). Unique per source_platform. |
reference* | The user-visible order or reference number. |
source_platform* | String identifying where the transaction originated (e.g. shopify, woocommerce, plenigo, or your own platform name). |
source_channel_id | Optional sub-channel within the platform (e.g. web, pos, mobile). |
invoice_number | Accounting reference, if you maintain one. |
Lifecycle
Field | Description |
state* | The authoritative lifecycle status. See 3. States |
financial_status | Free-form string carrying the source platform's own vocabulary (e.g. paid, pending, authorized). Useful when the platform's status doesn't map cleanly onto Unidy's state. |
fulfillment_status | Free-form string for shipping / fulfillment (e.g. fulfilled, unfulfilled, partial). |
placed_at* | When the order was placed (ISO 8601 timestamp). |
completed_at | When the order reached a terminal state. |
cancelled_at | When the transaction was cancelled. |
cancel_reason | Why the transaction was cancelled (free-form string). |
Money
All monetary amounts are returned as floats. currency is an ISO 4217 code (e.g. EUR, USD).
Field | Description |
currency | ISO 4217 currency code (e.g. EUR, USD). |
total | Total order amount. |
subtotal | Subtotal before tax, shipping, and discounts. |
total_tax | Total tax amount. |
total_shipping | Total shipping cost. |
total_discount | Total discount applied. |
total_paid | Amount paid so far. |
total_refunded | Amount refunded. |
exchange_rate | Exchange rate, only relevant when the recorded amounts are in a non-base currency. |
prices_include_tax | Whether prices include tax. Default false. |
tax_exempt | Whether the transaction is tax-exempt. Default false. |
Payment
Field | Description |
payment_method | Payment method used (e.g. credit_card, paypal, sepa). |
payment_provider_ref | The payment provider's reference (e.g. a Stripe charge ID). |
coupon_code | Applied coupon code, if any. |
Annotations
Field | Description |
tags | Array of strings the brand uses to classify the transaction. |
customer_note | Note left by the customer at checkout. |
staff_note | Internal note added by brand staff. |
order_type | Type of order (free-form, e.g. online, pos, subscription). |
platform_metadata | JSON object for anything platform-specific that doesn't fit a first-class field. |
Billing & shipping addresses
A transaction may have one billing address (billing_address key) and one shipping address (shipping_address key). Both use the same field structure. Each transaction can have at most one address of each kind.
Field | Description |
salutation | Salutation. One of mr, mrs, mx. |
first_name | First name. |
last_name | Last name. |
company_name | Company name. |
address_line_1 | Street line 1. |
address_line_2 | Street line 2. |
city | City. |
postal_code | ZIP / postal code. |
country_code | ISO 3166-1 alpha-2 country code (e.g. DE, US). |
phone_number | Phone number. |
Relationships
A Transaction is connected to other Unidy objects as follows:
- It belongs to one user — the customer.
- It is grouped under one Transaction Category — a brand-curated bucket (see Transaction Categories).
- It contains one or more line items — see Line items.
- It may have one billing address and / or one shipping address. Each transaction can have at most one of each.
Identity & deduplication
The pair (external_id, source_platform) is unique across all transactions in Unidy. This is the idempotency contract:
- Re-sending a transaction with the same pair is rejected on single-create and silently skipped in batch operations.
- Use the source platform's stable order ID as
external_id. Don't reuse the same ID across different platforms.
This guarantee means you can safely replay webhooks or retry failed pushes without creating duplicates.
3. States
A transaction is always in one of six lifecycle states:
State | Meaning |
open | Order placed, no payment recorded yet. Default initial value. |
partially_paid | Some payment received but the balance is non-zero. |
paid | Fully paid. |
partially_refunded | A refund has been issued but not for the full amount. |
refunded | The full amount has been refunded. |
cancelled | The order was cancelled. Pair with cancelled_at and cancel_reason. |
state is the authoritative lifecycle field — use it for filtering, reporting, and business logic. financial_status carries the source platform's own vocabulary and may be set independently for richer reporting.
4. Line items
A line item represents one entry on the transaction (one product or charge). A transaction can have any number of line items. Line items are returned under the line_items key in transaction payloads.
Line item fields
Field | Description |
id | Numeric ID assigned by Unidy on create. Line items use numeric IDs, not UUIDs. |
name* | Display name (e.g. Concert Ticket). |
quantity* | Units purchased (integer). |
external_product_id* | Your product identifier on the source platform. |
unit_price* | Price per unit (float). |
total_price* | Total for this line, after quantity (float). |
sku | Stock-keeping unit. |
variant | Variant identifier (e.g. Red / Large). |
category | Item category (free-form). |
item_type | Type of item (e.g. physical, digital, service). |
position | Display order within the transaction. |
seller_id | Marketplace seller ID. |
staff_note | Internal note for this line. |
total_discount | Per-line discount amount (float). |
total_tax | Per-line tax amount (float). |
tax_rate | Per-line tax rate (float, e.g. 0.19 for 19%). |
metadata | JSON object for anything platform-specific. |
5. Transaction Categories
A Transaction Category is a brand-curated bucket that every transaction must belong to. Typical examples: Shop Orders, Donations, Event Tickets, Subscription Plan A.
Categories are brand-scoped (a category can be shared by more than one brand within the same Unidy instance) and created in the Dashboard by a brand admin. There is no API to create or modify categories — this is intentional, so brands stay in control of their own taxonomy.
The V1 API exposes categories as read-only so your integration can look up the right category ID at startup. The typical flow is:
- A brand admin creates the categories they care about in the Dashboard.
- Your integration fetches the category list once at startup (or on a refresh interval) and caches the mapping from your platform's concept (e.g. "Shopify orders") to the corresponding Unidy category ID.
- Every transaction you push references the appropriate category by Unidy ID (
transaction_category_id).
6. Accessing transactions
Unidy exposes transactions through two distinct APIs, each meant for a different audience. The exact paths, parameters, and schemas are documented in Swagger — this section covers the conceptual model.
6a. V1 REST API — for partner backends (push & manage)
Used by a brand's backend or partner service to create, update, search, and delete transactions. It is not intended to be called from end-user devices.
- Authentication: OAuth 2.0 client credentials flow. The resulting access token is not bound to a specific end user.
- Authorization: access is scoped — separate scopes for read, write, and batch operations.
- Required feature flag: the
transactionsfeature must be enabled
❗ The V1 API is the primary integration path. If you have a custom platform without a dedicated connector, this is where you push your transactions.
Capabilities exposed by the V1 API:
- Create a single transaction.
- Create up to 100 transactions in a single batch call (with skip / error reporting for duplicates and validation failures).
- Update any subset of a transaction's fields; nested
line_itemsarrays support upsert and delete semantics (items without anidare created, items with anidare updated, items with anidand a_delete: trueflag are removed).
- Delete a transaction.
- Add, update, or remove individual line items on an existing transaction.
- Search, filter, and list transactions globally or per user.
- Read the list of available transaction categories.
Refer to the V1 REST API Swagger documentation for the full endpoint reference, request schemas, and response examples.
6b. SDK API — for brand apps (read on behalf of a user)
Used by a brand's mobile or web app to let an end user read their own order history. The SDK API is user-scoped server-side — a user can never see another user's transactions.
- Authentication: two headers per request — the SDK client's API key (
Authorization: Bearer) plus the end user's JWT (X-ID-Token).
- Capabilities: list and fetch transactions belonging to the authenticated user, with pagination, filtering (by state, financial status, source platform, etc.), and sorting (by
placed_at,created_at, ortotal).
Refer to the SDK API Swagger documentation for the full endpoint reference.
What a transaction looks like in API responses
The shape below is what the API returns for both list (under results[]) and single-transaction endpoints:
{
"id": "86668c4a-4073-5e81-b892-07393d11c60d",
"user_id": "78eb9bab-c334-48fb-8d13-109f16d703fe",
"transaction_category_id": "b402f79e-30ce-4c94-b4c6-f7443ce26b4b",
"external_id": "ORDER-123",
"reference": "REF-456",
"source_platform": "shopify",
"source_channel_id": "web",
"order_type": "online",
"state": "paid",
"financial_status": "paid",
"fulfillment_status": "fulfilled",
"currency": "EUR",
"payment_method": "credit_card",
"payment_provider_ref": "ch_3PqXyz",
"coupon_code": null,
"invoice_number": "INV-2026-001",
"prices_include_tax": false,
"tax_exempt": false,
"tags": ["vip", "first_order"],
"cancel_reason": null,
"customer_note": null,
"staff_note": null,
"platform_metadata": { "shopify_id": "12345" },
"total": 49.98,
"subtotal": 41.99,
"total_discount": 0.0,
"total_paid": 49.98,
"total_refunded": 0.0,
"total_shipping": 0.0,
"total_tax": 7.99,
"exchange_rate": null,
"placed_at": "2026-05-21T10:30:00.000Z",
"cancelled_at": null,
"completed_at": "2026-05-21T10:35:00.000Z",
"created_at": "2026-05-21T10:30:01.123Z",
"updated_at": "2026-05-21T10:35:00.456Z",
"billing_address": {
"salutation": "mr",
"first_name": "Max",
"last_name": "Mustermann",
"company_name": null,
"address_line_1": "Alexanderplatz 1",
"address_line_2": null,
"city": "Berlin",
"postal_code": "10178",
"country_code": "DE",
"phone_number": null
},
"shipping_address": null,
"line_items": [
{
"id": 1,
"name": "Concert Ticket",
"quantity": 2,
"sku": "SKU-001",
"variant": "Standing",
"category": null,
"item_type": null,
"position": 1,
"external_product_id": "PROD-123",
"seller_id": null,
"staff_note": null,
"metadata": null,
"unit_price": 20.99,
"total_price": 41.98,
"total_discount": 0.0,
"total_tax": 7.99,
"tax_rate": 0.19
}
]
}Notes on the wire format:
id,user_id, andtransaction_category_idare Unidy's public UUIDs, not internal numeric IDs.
- All monetary fields are returned as floats (not strings), and may be
nullwhen not set.
- All timestamps are ISO 8601 strings with millisecond precision.
billing_addressandshipping_addressare returned asnullwhen not set.
line_itemsis always an array (possibly empty).
7. Webhooks
Unidy fires transaction.created, transaction.updated, and transaction.deleted events when transactions are created, updated, or deleted. Subscriptions are configured per brand in the Dashboard.
For the full list of webhook events, subscription setup, and detailed payload bodies, see the Webhook Events page.
8. Integration patterns
Three common shapes for a Unidy transactions integration:
1. One-time import. Use the V1 batch create endpoint in pages of up to 100 transactions. Because (external_id, source_platform) is unique, replaying the same page after a timeout will not create duplicates — items already in Unidy are reported back as skipped. Process pages sequentially or with small concurrency to stay within rate limits.
2. Real-time sync from an external platform. Subscribe to your platform's native webhooks (for example, Shopify's orders/create, orders/updated, orders/cancelled). On each event, translate the payload and either create a new transaction (for new orders) or update an existing one (looking it up by external_id + source_platform). For state changes, send only the fields that changed (state, total_paid, total_refunded, etc.) — updates are partial by default.
3. Brand app showing order history. In the brand's mobile or web app, list the authenticated user's transactions via the SDK API, sorted by placed_at descending. The list response already includes line items and addresses, so a detail view often doesn't need a second call.