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 print job finishes |
| SHIPPED | Handed to the carrier. tracking is populated. | An operator |
| DELIVERED | Marked delivered. | 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, 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.