A connected store, from connect to delivery
A connected store holds two keys and no secret key: its integration key
(ik_live_…), for its server, and its publishable key (pk_live_…),
for its pages, where the designer runs. This guide follows one customer’s
item through such a store, from the store connecting to the order arriving,
and names the key each call takes. Every call below that the store’s server
makes takes its integration key, which reaches the store’s own designs,
prints, orders and share links, and the workspace’s templates, blanks and
printshops (Connecting a store).
| Step | Who calls | Key |
|---|---|---|
| The customer approves a proof | The store’s page, or a hosted page | Publishable key, or the hosted page’s link |
| Commit, re-reference, read, delete a design | The store’s server | Integration key |
| Approve a design, hand out a proof page | The store’s server | Integration key |
| Print and order | The store’s server | Integration key |
| Hear what happened | Pixonaut, to the store’s endpoint | Signed with the endpoint’s secret |
| Catch up on missed events | The store’s server | Integration key |
1. Connect
The merchant clicks connect in the store, approves in the studio, and the store exchanges the code it is sent back with for its integration key, its publishable key and its webhook endpoint (Connecting a store). Keep the integration key and the endpoint’s secret on the store’s server, and hand only the publishable key to its pages.
2. The customer approves a proof
On the product page, the designer runs with the publishable key, and the
customer approves what they made: approve() renders a proof and answers a
proofToken and a proofUrl. A store that sends its customer to the hosted
designer instead gets the proofToken back on its return address
(Sending the customer back to the store).
Either way, nothing is stored yet: the token is the customer’s approved
picture, waiting for the store to commit it.
The token reached the store through the customer’s browser. Keep it with the cart line on the store’s server once the line is priced, and commit that copy (Cart lifetimes and proof expiry).
3. Commit the design with the store’s reference
The store’s server commits the proof with its integration key, with the cart
line as its own reference, externalRef, and any order data its printshop
needs:
POST /v1/designs
Authorization: Bearer ik_live_...
Idempotency-Key: cart-10482-1:commit
Content-Type: application/json
{
"proofToken": "eyJhdWQiOiJwcm9vZiIsImlkIjoicHJmXzljMWUyZDNmNGE1YjZjN2Q4ZTlmMGExYiIsInQiOiJhY21lIiwiZXhwIjoxNzkyNDkxMjQ3MDAwfQ.EXAMPLE_signature_not_a_real_one_0000000000",
"externalRef": "cart-10482-1",
"fulfilmentPayload": { "variantSku": "TEE-BLACK-M" }
}
{
"designId": "6f1c8a2b-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
"printFloorDpi": 150,
"lowResolution": []
}
externalRef, fulfilmentPayload and printshopId are a server’s fields:
the store’s page, with its publishable key, is refused them
secret_key_required. A proof token commits once; committing it again answers
409 proof_already_committed with the design it became
(Placing an order safely).
Before taking payment, read the design back with GET /v1/designs/{id} and
check its print size, template and product against what the line was priced
as (Cart lifetimes and proof expiry).
When the cart becomes an order, point the design at the order line with
PUT /v1/designs/{id}/external-ref:
{ "externalRef": "WEB-10482-1" }
GET /v1/designs?externalRef=WEB-10482-1 then finds it.
4. Approve it, with the customer’s consent
Once the customer has paid, the store’s server approves the design with
POST /v1/designs/{id}/approve, and says how the customer agreed, so the
design keeps it: by adding it to the cart, by ticking a box, or by approving
it again on the store after correcting it (edit). ref is the store’s own
reference for where it was given:
{
"consent": { "kind": "checkbox", "at": 1790000050000, "ref": "WEB-10482" },
"expectedRevision": 1
}
{ "status": "approved" }
design.approved carries the consent and the revision it approved:
{
"id": "1790000060000-5d7e1f2a-3b4c-4d5e-8f60-718293a4b5c6",
"type": "design.approved",
"createdAt": 1790000060000,
"payload": {
"designId": "6f1c8a2b-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
"approvedVia": "explicit",
"revision": 1,
"consent": { "kind": "checkbox", "at": 1790000050000, "ref": "WEB-10482" }
}
}
afterDays approves later instead, and expectedRevision refuses the
approval revision_changed when the design was corrected since the store
read it (Design to order).
5. Or let the customer decide on a proof page
A store that wants the customer to approve the finished proof, rather than
approving it on their behalf, hands out a deciding proof page with
POST /v1/share-links and sends the customer its url, by email or from
their account page:
{
"kind": "proof-page",
"subjectId": "6f1c8a2b-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
"mayDecide": true,
"locale": "sv"
}
{
"id": "0b1c2d3e-4f50-4a6b-8c7d-000000000010",
"kind": "proof-page",
"subjectId": "6f1c8a2b-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
"mayDecide": true,
"returnUrl": null,
"name": null,
"brandId": null,
"designLimit": null,
"printAreaFrame": false,
"placementFrame": false,
"locale": "sv",
"createdAt": 1790000050000,
"expiresAt": 1792592050000,
"token": "EXAMPLE_share_link_token_not_a_real_one_000",
"url": "https://app.pixonaut.example/h/proof/EXAMPLE_share_link_token_not_a_real_one_000?lang=sv"
}
The customer approves, rejects or asks for changes there. An approval on the
page keeps a proof_page consent naming the link, and emits
design.approved as above; a request for changes emits
design.changes_requested, and with a returnUrl the page sends the
customer back to the store to correct the design
(Sending the customer back to the store).
A link the store’s key handed out stops working when the store disconnects.
6. Print and order
POST /v1/prints makes the approved design’s print files, answering 202
while it renders; once GET /v1/prints/{id} reads completed, or
print.completed arrives, POST /v1/prints/{id}/orders sends the completed
print to the printshop,
with the order line as externalRef and who it ships to:
POST /v1/prints/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/orders
Authorization: Bearer ik_live_...
Idempotency-Key: WEB-10482-1:order
Content-Type: application/json
{
"quantity": 1,
"externalRef": "WEB-10482-1",
"recipient": { "name": "Alexandra Lind", "email": "[email protected]" },
"shippingAddress": {
"line1": "12 High Street",
"postalCode": "SW1A 1AA",
"city": "London",
"country": "GB"
}
}
An item printed on several placements is ordered as one with
POST /v1/orders (Front and back on one item).
Send an Idempotency-Key with each, so a retried request makes nothing twice
(Placing an order safely). What each answers,
and what the order goes through from there, is in
Design to order. The integration key also cancels an
order (POST /v1/orders/{id}/cancel), retries a failed one
(POST /v1/orders/{id}/retry), and settles one whose outcome is unknown
(PATCH /v1/orders/{id}).
7. Hear what happened, and catch up
The store’s endpoint receives the events about the store’s own work, and
about the workspace’s templates, blanks and printshops, signed with the
endpoint’s secret (Receive webhooks): the
design’s design.committed and design.approved, the print’s
print.completed, and the order’s order.submitted and each
order.status_changed up to shipped. Narrow them to what the store handles
with PATCH /v1/webhooks/{id} on its own endpoint
(Connecting a store).
A store that was down misses nothing for good. Deliveries are retried
(Webhooks gives the schedule), and the store’s server pulls
the events in the order they were recorded with GET /v1/events, narrowed
with types to the ones it subscribes to. Keep the pull’s own checkpoint,
the id of the last event a pull answered, and pass it as after; leave
after out on the first pull. Never move the checkpoint on because a webhook
arrived: deliveries can arrive out of order, and an event that failed while a
later one was delivered would then be skipped.
GET /v1/events?after=1790000060000-5d7e1f2a-3b4c-4d5e-8f60-718293a4b5c6&types=order.status_changed
Authorization: Bearer ik_live_...
{
"events": [
{
"id": "1790086400000-0f1e2d3c-4b5a-4968-8776-655443322110",
"type": "order.status_changed",
"createdAt": 1790086400000,
"payload": {
"orderId": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"printId": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"status": "shipped",
"previousStatus": "printed"
}
}
],
"nextCursor": null
}
Each event comes in the envelope a delivery carries, with the same id, so
the store drops one it already handled by webhook. Follow nextCursor as the next
after until it is null. The pull answers the events the endpoint
receives: never one about another store’s work.
8. Clean up a cart nobody bought
A design committed for a cart the customer abandons is deleted with
DELETE /v1/designs/{id}, which answers 204 and emits design.deleted.
A design that was approved or printed stays
(Placing an order safely).
9. Erase a customer’s personal data
When a customer asks to be erased, the store’s server erases the delivery a
design holds with DELETE /v1/designs/{id}/delivery, and the recipient and
address of each order once it has shipped, failed or been cancelled, with
POST /v1/orders/{id}/erase-personal-data. It finds the orders by its own
reference, GET /v1/orders?externalRef=
(Erasing personal data).
When the store goes away
Before the store is removed, or when the merchant disconnects it, the
store’s server calls DELETE /v1/connect with its integration key. Its
integration key, its publishable key, its webhook endpoint and every share
link it handed out stop working at once. Its designs, prints and orders
stay with the workspace, and the same site connecting again keeps working
with them (Connecting a store).
Questions? Write to [email protected].