Skip to content

Push your sales events

Sales events are purchases and returns from your point of sale or data warehouse. Ingesting them powers RFM segmentation, analytics, and SMS attribution. This is usually the highest-volume integration, so it is built to be safe and forgiving.

A sale references a store, and unlike a profile, a store is not auto-created. The store field is a reference (your store external_id or a canonical store_ id), and the call returns 404 if it resolves to no store. Sync your stores first, then push sales against them.

POST /v1/sales-events records one transaction. Amounts are always in cents and never divided. A RETURN is recorded as its own separate event: it does not modify or void any earlier PURCHASE. Analytics net purchases and returns together; you never edit a past event.

Terminal window
curl https://api.sms.noticia.ai/v1/sales-events \
-X POST \
-H "x-api-key: ntca_REPLACE_ME" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f1c0b9e-2a4d-4c8e-9b1a-3e5f6a7b8c9d" \
-d '{
"external_id": "TICKET-2026-04-25-001234:PURCHASE",
"type": "PURCHASE",
"occurred_at": "2026-04-25T14:32:00+02:00",
"store_id": "paris-rivoli-01",
"currency": "EUR",
"profile_id": "crm-customer-4815",
"total_amount_cents": 12500,
"total_discount_cents": 0,
"total_tax_cents": 2083,
"source": "Cegid POS v3",
"line_items": [
{
"external_id": "LINE-001",
"product": {
"external_id": "SKU-12345",
"name": "White T-shirt M",
"properties": { "category": "Menswear", "size": "M", "color": "white" }
},
"quantity": 2,
"unit_price_cents": 5000,
"total_price_cents": 10000,
"tax_cents": 1667
},
{
"external_id": "LINE-002",
"product": { "external_id": "SKU-67890", "name": "Tote bag" },
"quantity": 1,
"unit_price_cents": 2500,
"total_price_cents": 2500,
"tax_cents": 416
}
]
}'

A 201 Created returns the stored sales event with its prefixed evt_ id (or 200 OK if this external_id was already ingested).

Only a few fields are strictly required: type, occurred_at, store, currency, total_amount_cents, total_discount_cents, and at least one line item with quantity and total_price_cents. Everything else (profile, product, payments, loyalty, source, metadata) is optional but makes the data far more useful.

Each line item can carry a product. It is optional (omit it for a service, shipping or fee line), but for retail you almost always want it:

  • external_id: your SKU or product code.
  • name: a human-readable label.
  • properties: a free-form bag of scalar attributes (string, number or boolean), up to 50 keys and 4 KB total. This is where category, size, color, material, and similar product traits go.

Product properties need no setup: send whatever keys you have and they are stored as-is. Keep keys consistent across line items so analytics group cleanly. You can review your product taxonomy in the Noticia app under Integrations -> Product catalog.

  • Anonymous sale: omit profile entirely. The sale is recorded but not attributed to a customer.
  • Unknown buyer: if profile_id is a CRM id you have not pushed yet, Noticia auto-creates a light profile from it rather than rejecting the sale (a canonical prof_ id, by contrast, must already exist). Your POS can push tickets in real time, before the loyalty profile has synced; when your CRM later upserts the full profile, the history lines up on the same external_id.

This means you can push sales and sync profiles in any order. Stores are the exception: they must exist first.

  • All monetary fields end in Cents and are integers. Keep them raw; divide by 100 only when you display them.
  • currency is one of EUR, GBP, USD, CHF.
  • total_amount_cents is the amount actually paid, net of discount and tax-inclusive, always positive (a RETURN is the absolute amount).

Send each event with a stable external_id: replaying a failed batch is safe because already-ingested events are recognized and not duplicated. Add an Idempotency-Key on retries as an extra transport safety net, and apply the recommended back-off for a robust pipeline.