Track sales in your own system

A worked example: carrying your own references and fulfillment states on CardNexus sales, so your ERP, WMS, or back office can drive them without keeping a separate table keyed on order numbers.

Every sale has a metadata object — your own keys, your own values. CardNexus never reads or changes them, and the buyer never sees them. You can filter your sales by them, which is what makes them a working queue rather than just a note.

1. Stamp your reference when the order arrives

When an order comes in, write your own identifier onto it with PATCH /v1/sales/{orderNumber}/metadata:

curl -X PATCH https://public-api.cardnexus.com/v1/sales/OR-ASNBC-1/metadata \
  -H "Authorization: Bearer cnk_live_4f2d8c91e5b34a7f9e6c2d1a8b5f7e3c" \
  -H "Idempotency-Key: 9b4e7c20-5d1a-4f8e-b3c6-a7d9e1f2c4b8" \
  -H "Content-Type: application/json" \
  -d '{
    "metadata": {
      "internal_order_ref": "SO-10432",
      "fulfillment_stage": "received"
    }
  }'

The response is the full sale, with your keys under metadata:

{
  "orderNumber": "OR-ASNBC-1",
  "status": "pending_shipment",
  "metadata": {
    "internal_order_ref": "SO-10432",
    "fulfillment_stage": "received"
  }
}

Keys start with a letter and may contain letters, digits, _, . and -, up to 64 characters. Values are strings of up to 500 characters, and a sale holds at most 50 keys.

2. Move the order through your own stages

CardNexus tracks an order as pending_shipment until you mark it shipped. If your process has steps in between — picking, packing, waiting on a restock — put them in metadata and advance them as they happen:

curl -X PATCH https://public-api.cardnexus.com/v1/sales/OR-ASNBC-1/metadata \
  -H "Authorization: Bearer cnk_live_4f2d8c91e5b34a7f9e6c2d1a8b5f7e3c" \
  -H "Content-Type: application/json" \
  -d '{"metadata": {"fulfillment_stage": "packed", "packed_by": "camille"}}'

The write merges. Keys you send are set, keys you leave out keep their value, so internal_order_ref from step 1 is still there. That also means two of your systems can each own their own keys on the same sale without overwriting each other.

To remove a key, send it as null:

{"metadata": {"packed_by": null}}

An empty string is a value, not a removal. Sending {} changes nothing.

3. Pull your work queue

Filter your sales on any of your keys with metadata[key]=value on GET /v1/sales. Everything still on the packing bench:

curl -G https://public-api.cardnexus.com/v1/sales \
  -H "Authorization: Bearer cnk_live_4f2d8c91e5b34a7f9e6c2d1a8b5f7e3c" \
  --data-urlencode "status=pending_shipment" \
  --data-urlencode "metadata[fulfillment_stage]=packed"

Repeat the parameter with another key to require both — up to 10 keys per call:

?metadata[fulfillment_stage]=packed&metadata[warehouse]=lyon

Matching is exact and case-sensitive: there is no partial or prefix match, so SO-104 will not find SO-10432. Combine it freely with status, placedFrom and placedTo, and page through the results with pagination.nextCursor as usual.

4. Ship and stamp in one call

POST /v1/sales/{orderNumber}/mark-shipped takes the same metadata object, so the last stage change can ride along with the tracking number:

curl -X POST https://public-api.cardnexus.com/v1/sales/OR-ASNBC-1/mark-shipped \
  -H "Authorization: Bearer cnk_live_4f2d8c91e5b34a7f9e6c2d1a8b5f7e3c" \
  -H "Idempotency-Key: 2c7f1a94-8e3b-4d6a-9f1c-5b8e2a4d7c31" \
  -H "Content-Type: application/json" \
  -d '{
    "trackingNumber": "6A18432197431",
    "metadata": {"fulfillment_stage": "shipped"}
  }'

The two either both apply or neither does: a tracking number rejected as invalid leaves your metadata untouched, and metadata that would take the sale past its key limit returns 422 METADATA_LIMIT_EXCEEDED without shipping the order.

5. Match webhooks back to your records

The order.status.changed webhook carries your metadata on the seller's copy, so a delivery can be routed to the right record in your system without a follow-up call:

{
  "type": "order.status.changed",
  "recipientRole": "seller",
  "data": {
    "orderNumber": "OR-ASNBC-1",
    "status": "delivered",
    "previousStatus": "shipped",
    "metadata": {
      "internal_order_ref": "SO-10432",
      "fulfillment_stage": "shipped"
    }
  }
}

data.metadata is null on the buyer's copy of the same event — metadata is yours alone.

Reading it back

metadata is on every sales response: GET /v1/sales, GET /v1/sales/{orderNumber}, and the responses to mark-shipped and cancel. It never appears on the buyer's purchase.

Metadata can be written in any status, including on completed and cancelled sales — so a reconciliation run can still stamp an invoice number long after the order closed.