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 APISDK API). This guide focuses on concepts, structure, and integration flows.


1. Overview

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. shopifywoocommerceplenigo, or your own platform name).
source_channel_id
Optional sub-channel within the platform (e.g. webposmobile).
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. paidpendingauthorized). Useful when the platform's status doesn't map cleanly onto Unidy's state.
fulfillment_status
Free-form string for shipping / fulfillment (e.g. fulfilledunfulfilledpartial).
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. EURUSD).

Field
Description
currency
ISO 4217 currency code (e.g. EURUSD).
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_cardpaypalsepa).
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. onlinepossubscription).
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 mrmrsmx.
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. DEUS).
phone_number
Phone number.

Relationships

A Transaction is connected to other Unidy objects as follows:

  • It belongs to one user — the customer.
  • 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. physicaldigitalservice).
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

Transaction Category is a brand-curated bucket that every transaction must belong to. Typical examples: Shop OrdersDonationsEvent TicketsSubscription 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:

  1. A brand admin creates the categories they care about in the Dashboard.
  1. 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.
  1. 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 transactions feature 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_items arrays support upsert and delete semantics (items without an id are created, items with an id are updated, items with an id and a _delete: true flag 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_atcreated_at, or total).

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:

  • iduser_id, and transaction_category_id are Unidy's public UUIDs, not internal numeric IDs.
  • All monetary fields are returned as floats (not strings), and may be null when not set.
  • All timestamps are ISO 8601 strings with millisecond precision.
  • billing_address and shipping_address are returned as null when not set.
  • line_items is always an array (possibly empty).

7. Webhooks

Unidy fires transaction.createdtransaction.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/createorders/updatedorders/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 (statetotal_paidtotal_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.

Did this answer your question?
😞
😐
🤩