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 last farm print job linked to the order completes, or an operator
SHIPPEDHanded to the carrier. tracking is populated.An operator
DELIVEREDThe carrier reported delivery.The daily carrier check, or 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 comes from a check we run once a day against the carrier, or from an operator, so it can trail the parcel's arrival by up to a day. An order whose carrier was recorded as Other is never checked, and reaches DELIVERED only by hand.

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 on the REST API, so a hundred open orders is a hundred requests — space them out rather than firing them together. The MCP server's list_my_orders tool takes the same key and returns your 20 most recent orders in one call; see /docs/api/mcp.

What arrives without asking#

Email, and nothing else. When an API order is paid, a confirmation goes to every address in the order's email field (the account address unless the request named others, up to five) with the code, a one-line summary, the total charged and the shipping address. After that the same addresses get an email when the order is printed, when it ships (with tracking) and when it is delivered. Printed goes out on its own when the last farm print job for the order completes, and delivered when the daily carrier check sees the parcel arrive; the rest go out when an operator moves the order on. Each address gets its own message, so the people on an order do not see each other's addresses. Accepted and printing send nothing, a correction backwards sends nothing, and nothing is pushed to your systems.

Store the code at submission
The order code is the only handle the REST API gives you. No REST endpoint lists your orders, and there is 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. If you do lose one, the MCP tool list_my_orders (same key) and the /orders page (signed in as the key's account) both list API orders with their codes.

Questions

Does the X3D API support webhooks?

No. There is no outbound webhook system: no field to register a callback URL, no signing secret issued to customers, and no code that posts anything to a customer endpoint. Follow an order by polling GET /api/print/orders/{code}.

What are the /api/webhook endpoints in X3D then?

They are inbound. /api/billing/webhook receives signature-verified events from our payment processor, /api/webhook is a legacy alias for the same handler, and /api/order-status accepts progress reports from the print farm behind a shared secret. None can be subscribed to, and the secrets are ours, not yours.

How often should I poll an X3D order?

Every few minutes is plenty. A print runs for hours and most transitions are made by an operator, so a five-minute poll sees everything a one-second poll would. Nothing rate-limits the status endpoint today, which is a reason to be considerate rather than a reason not to be.

Will X3D email me when my API order ships?

Yes. Every address on the order gets the confirmation when it is paid, then an email when it is printed, when it ships (with the tracking number) and when it is delivered. Accepting the order and starting the print send nothing. Email is all that is sent: nothing is posted to your systems, so a program that needs to react to a change still polls.

Related guides