Skip to content
Create API key

Ingest

For events, a 202 with an event id means the event is recorded. If it can’t be recorded you get 503 ingest_unavailable. Retry with backoff on 503.

Publishable or secret key.

Field Type Notes
type string Required. For example page_view, product_view, add_to_cart, conversion, or a custom name
visitorRef string Your anonymous visitor reference
url string Page URL
productId string Product the event concerns
value number Monetary value, such as a conversion amount
properties object Free-form additional attributes
Terminal
curl -X POST https://api.plunk.io/cx/v1/ingest/event \
-H "Authorization: Bearer pk_live_••••" -H "Content-Type: application/json" \
-d '{
"type": "product_view",
"visitorRef": "v_84af",
"productId": "SF-1042"
}'
202 Accepted
{
"accepted": true,
"eventId": "9d1c…",
"type": "product_view"
}

A dealer or rep portal can send "type": "reorder_request" with properties: { customerRef, lines: [{ productId, quantity, note }], poNumber, note }. Because it creates an order request, send it from your server with a secret key. A publishable key gets 403 secret_key_required.

It records the event as usual and, when your account runs the Plunk Commerce order desk, also creates an order request draft. The response then adds orderRequestId and orderRequestNumber. See order requests.

Associates an anonymous visitor with a known identity. visitorRef is required, plus at least one of email, hashedEmail or userId. An optional traits object rides along.

202 Accepted
{
"accepted": true,
"eventId": "41be…",
"visitorRef": "v_84af"
}

Secret key only. Records a settled order as real sales data. Revenue dashboards, product and store performance, customer purchase history and lifecycle stages all read from it. This is the endpoint a payment integration calls from its webhook handler: wire it to Stripe’s checkout.session.completed or invoice.paid and your sales arrive as they happen.

  • Posting the same orderId again replaces that order’s lines, so webhook retries and corrections are safe.
  • The buyer behind customerRef is created on first sight and linked to the sale. A first sale moves them to the Customer lifecycle stage automatically.
  • A publishable key gets 403 secret_key_required.
Field Type Notes
orderId string Required. Stable per order, such as the Stripe session or invoice id. Reposting it replaces the order’s lines.
customerRef string Required. Your stable id for the buyer, such as the Stripe customer id.
lines[].productId string Required. Joins your catalog when the id matches. Unknown ids still count as revenue.
lines[].price number Required. The extended line total (already multiplied by quantity). Up to 500 lines per order.
lines[].quantity integer Optional. Defaults to 1.
lines[].description string Optional line description.
email string Optional but recommended. Fills the buyer’s contact record.
customerName string Optional. Defaults to email or customerRef.
orderDate datetime Optional. Defaults to now.
storeId · channel · currency string Optional. channel defaults to api.
status string Optional. Defaults to Shipped (settled). Pass an open-order status only for unsettled orders: they report as backlog, not revenue.
Terminal
curl -X POST "https://api.plunk.io/cx/v1/ingest/sale" \
-H "Authorization: Bearer sk_live_••••" -H "Content-Type: application/json" \
-d '{
"orderId": "cs_a1B2c3",
"customerRef": "cus_R8xT2m",
"email": "buyer@example.com",
"channel": "stripe",
"currency": "USD",
"lines": [
{"productId": "SF-1042", "description": "Harbor Sofa", "quantity": 2, "price": 2398.00}
]
}'
200 OK
{
"accepted": true,
"orderId": "cs_a1B2c3",
"linesWritten": 1,
"customerId": "43f7…",
"customerCreated": true
}

Two posts for the same new buyer at the same moment can return 503 ingest_busy. Retry with backoff.

Secret key only. Your ERP’s order status reports: one call per status change, or a batch of up to 500 orders. erpOrderId is the join key to the order’s sales rows, so open and invoiced orders report correctly everywhere. orderRequest or poNumber links the first report to a portal order request. Status words you send are stored as given.

Field Type Notes
orders[].erpOrderId string Required. Your ERP order number (the orderId on its sales rows).
orders[].status string Required. For example Open, Shipped, Invoiced, Cancelled.
orders[].statusDate datetime Optional. Defaults to now.
orders[].orderRequest · poNumber · customerRef string Optional. Link the report to a portal order request.
orders[].lines[] array Optional: { productId, ordered, shipped, backordered, eta }
orders[].shipments[] array Optional: { carrier, tracking, shippedDate, lines: [{ productId, qty }] }
orders[].note string Optional.
Request
curl -X POST "https://api.plunk.io/cx/v1/ingest/order-status" \
-H "Authorization: Bearer sk_live_••••" -H "Content-Type: application/json" \
-d '{"orders":[
{"erpOrderId":"SO-55120","status":"Shipped","poNumber":"PO-7781",
"shipments":[{"carrier":"UPS","tracking":"1Z999…","shippedDate":"2026-10-13",
"lines":[{"productId":"SF-1042","qty":2}]}]}
]}'
200 OK
{
"accepted": 1,
"rejected": 0,
"orders": [
{
"erpOrderId": "SO-55120",
"orderRequestId": "…",
"orderRequestNumber": "OR-1042",
"linked": true,
"from": "Open",
"to": "Shipped",
"salesRowsUpdated": 3,
"error": null
}
]
}

You get 422 invalid_status_report when no order in the call was accepted (each row’s error says why), and 404 not_tracked when your account doesn’t run the order desk.

Secret key only, scope inventory:write. Push stock levels from your own system. This updates business data and changes product availability. The same numbers can arrive by CSV feed or the Shopify connection instead. All three write identically, so use whichever your stack produces most easily.

Field Type Notes
items[].sku string Required. Must match a product id in your catalog. Unknown SKUs are skipped and counted in the response. Connect the product feed first.
items[].source string Required. Warehouse or location name. A source containing “transit” counts as sellable inbound stock.
items[].qtyOnHand number Units on hand at that source.
items[].qtyBackordered number Optional.
items[].availableDate date Optional. When inbound units become sellable.
items[].asOf datetime Optional. Defaults to now.
Request
curl -X POST "https://api.plunk.io/cx/v1/ingest/inventory" \
-H "Authorization: Bearer sk_live_••••" -H "Content-Type: application/json" \
-d '{"items":[
{"sku":"SF-1042","source":"Main Warehouse","qtyOnHand":6,"qtyBackordered":4},
{"sku":"SF-1042","source":"In Transit","qtyOnHand":30,"availableDate":"2026-10-12"}
]}'
200 OK
{
"accepted": true,
"itemsWritten": 2,
"skusAggregated": 1,
"availabilityUpdated": 1,
"unknownSkusSkipped": 0,
"sources": ["Main Warehouse", "In Transit"]
}
  • Each request replaces the sources it carries and leaves other sources alone, so a snapshot per warehouse is safe to send independently.
  • Up to 5,000 items per request. Page larger snapshots.
  • Availability follows the numbers: on hand means in stock, inbound only means backorder, neither means out of stock.
  • If no SKU in the push matches your catalog, the response is 422 no_matching_skus.
Was this helpful?

Still stuck? Contact support and include the job or page you were on.