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}.
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.
/api/billing/webhookSigned webhookPayment events arriving from Stripe, verified against our own signing secret.
/api/webhookSigned webhookA legacy alias kept for an already-configured URL. It forwards to the handler above.
/api/order-statusSigned webhookThe print farm reporting progress on a website order, behind a shared internal secret.
| Endpoint | Who calls it | How it authenticates | Can you use it? |
|---|---|---|---|
| /api/billing/webhook | Stripe | A 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/webhook | Stripe | Identical — it hands the request straight to the billing handler. | No. |
| /api/order-status | Our own print farm | An 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. |
Poll instead#
/api/print/orders/{code}API keyThe 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.
| status | What has happened | Set by |
|---|---|---|
| PENDING_PAYMENT | The card charge failed. Nothing will happen until it is paid. | Submission |
| RECEIVED | Paid, waiting for an operator to accept it. | The successful charge |
| ACCEPTED | Queued to a printer, not started. | An operator |
| PRINTING | On the plate now. | An operator |
| PRINTED | Off the plate, being inspected and packed. | Automatic when the last farm print job linked to the order completes, or an operator |
| SHIPPED | Handed to the carrier. tracking is populated. | An operator |
| DELIVERED | The carrier reported delivery. | The daily carrier check, or an operator |
| CANCELLED | Stopped before printing. | An operator |
| REJECTED | The farm declined the job, usually an unprintable file. | An operator |
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
doneHow 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.
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.