X3DStudios

Webhooks

X3D sends no outbound webhooks and orders carry no callback URL, so polling GET /api/print/orders/{code} is the supported way to follow an order.

X3D does not send webhooks. There is no field on an order for a callback URL, no page for registering an endpoint, no signing secret issued to API customers, and no code anywhere that posts an event to a customer's server. If you need to know when an order moves, you ask — poll GET /api/print/orders/{code}.

There is nothing to configure
This page exists because "webhooks" is the first place developers look. If you are searching for where to paste your endpoint URL, stop looking: that setting does not exist, and no combination of fields on a submission will make us call you back.

The webhook endpoints in X3D are inbound#

Three paths in this codebase have webhook in their name or their job. All three receive traffic; none send it, and none are addressable by an API customer.

POST/api/billing/webhookSigned webhook

Payment events arriving from Stripe, verified against our own signing secret.

POST/api/webhookSigned webhook

A legacy alias kept for an already-configured URL. It forwards to the handler above.

POST/api/order-statusSigned webhook

The print farm reporting progress on a website order, behind a shared internal secret.

EndpointWho calls itHow it authenticatesCan you use it?
/api/billing/webhookStripeA stripe-signature header verified against our webhook secret. No signature is a 400; a bad one is a 400.No. Only Stripe holds a signature we accept.
/api/webhookStripeIdentical — it hands the request straight to the billing handler.No.
/api/order-statusOur own print farmAn x-internal-key header compared against a secret held in our deployment.No. The secret is not issued to customers, and it moves website orders, not API orders.
Even the internal callback does not touch API orders
/api/order-status updates the order records created by the website checkout and emails the customer about them. Orders placed through POST /api/print/orders are a separate record with its own lifecycle, and nothing in that internal path writes to them.

Poll instead#

GET/api/print/orders/{code}API key

The supported way to follow an order. Same key, same header as submitting.

Poll the code you got back at submission. The response carries status, a human-readable statusLabel, paid, the price, and a tracking object that stays null until the order ships.

statusWhat has happenedSet by
PENDING_PAYMENTThe card charge failed. Nothing will happen until it is paid.Submission
RECEIVEDPaid, waiting for an operator to accept it.The successful charge
ACCEPTEDQueued to a printer, not started.An operator
PRINTINGOn the plate now.An operator
PRINTEDOff the plate, being inspected and packed.Automatic, when the print job finishes
SHIPPEDHanded to the carrier. tracking is populated.An operator
DELIVEREDMarked delivered.An operator
CANCELLEDStopped before printing.An operator
REJECTEDThe farm declined the job, usually an unprintable file.An operator
Statuses can skip, and they can go backwards
Nothing validates the transitions: an operator can set any of the eight non-payment values directly, so an order can jump from RECEIVED to SHIPPED between two polls, and could in principle move back a step. React to the status you read rather than assuming the one that ought to come next.
CODE=X3D-K7M2QP

while :; do
  BODY=$(curl -sS -H "Authorization: Bearer $X3D_API_KEY" \
    "https://x3dstudios.com/api/print/orders/$CODE")

  STATUS=$(echo "$BODY" | jq -r .status)
  echo "$(date -u +%H:%M) $STATUS"

  case "$STATUS" in
    SHIPPED|DELIVERED|CANCELLED|REJECTED)
      echo "$BODY" | jq -r '.tracking // "no tracking"'
      break
      ;;
  esac

  sleep 300
done
Stop at SHIPPED, not DELIVERED
SHIPPED is the point where tracking appears and the carrier takes over — from there, the carrier's own API knows more than we do. DELIVERED is marked by hand on our side, so a loop that blocks until DELIVERED can wait a long time after the parcel has arrived.

How often to poll#

  • Every 5 minutes is a sensible default. Prints run for hours and most transitions are made by a person, so faster polling learns nothing sooner.
  • Poll only orders that are still open. Once you see SHIPPED, CANCELLED or REJECTED, stop — nothing further will change that matters to you.
  • The status endpoint has no rate limit in the application, so there is nothing to back off from and nothing to catch you either. Be considerate.
  • Poll per order. There is no list endpoint, so a hundred open orders is a hundred requests — space them out rather than firing them together.

What arrives without asking#

One email, once. When an API order is paid, a confirmation goes to the order's email address — the account address unless you overrode it in the request — with the code, a one-line summary and the shipping address. After that the order moves in silence: no email when it is accepted, printed or shipped, and nothing pushed anywhere.

Store the code at submission
The order code is the only handle on an order. There is no endpoint that lists your orders, and no way to look one up by file name, address or date. Write the code to your own records in the same transaction that triggered the submission — losing it means mailing [email protected] to find the order again.