All help topics
Settings and account
API and webhooks
Connect your own systems, Zapier or Make: read and add orders and customers with the API, and get told when things happen with webhooks.
The API lets your own systems, or tools like Zapier and Make, read your orders, customers, quotes and products, and add orders and customers. Webhooks work the other way: ArtworkDesk sends a message to an address of yours when something happens, such as an order being dispatched.
The API and webhooks are part of the Business plan. They aren't included in the free trial, which has the Pro plan's features.
Open Settings > API and webhooks. You need the Settings and team permission (Owners and Admins have it).
API keys
Every request to the API needs a key. Make a separate key for each system that uses it, so you can remove one without affecting the others.
- Under API keys, enter what it's for in New key for, for example "Zapier" or "Our website".
- Tick Can add orders and customers if it needs to add things. Leave it unticked for a key that can only read.
- Click Make a key.
- Click Copy and paste the key somewhere safe straight away. It starts with awd_.
Each key is listed with its name, the first few characters of the key, whether it Reads only or Reads, and adds orders and customers, and when it was last used.
To remove a key, click Remove next to it and confirm. Anything using it stops working straight away.
Webhooks
- Under Webhooks, paste the address to send to in Send to. It must start with https://.
- Under When, tick the events you want. An order (or part of it) is dispatched is ticked to start with.
- Click Add webhook.
- Click Send a test to check the address answers.
Each webhook shows its address, the events it's for and when it last sent something, with:
- Working: the last message went through.
- Failing: the last message didn't go through. The reason is shown underneath.
- Off: it isn't sending.
Click Edit to change the events, untick or tick Sending, see the Signing secret (click Show), or Remove the webhook. When editing, you can also tick Every event, which includes any events added in future. Click Save when you're done.
If an address fails 50 times in a row, the webhook is switched off and says so. Fix the problem, then tick Sending and click Save to switch it back on.
The basics
The rest of this article is for whoever sets up the connection.
The base address is your ArtworkDesk address followed by /api/v1:
https://yourbusiness.artworkdesk.com/api/v1
The exact address is shown under Using the API on the API and webhooks page. If your own domain is live, you can use that instead.
Send your key as a bearer token, and ask for JSON, on every request:
GET https://yourbusiness.artworkdesk.com/api/v1/orders
Authorization: Bearer awd_...
Accept: application/json
Send Content-Type: application/json with a JSON body when adding things. Everything that comes back is JSON.
Endpoints
| Method and path | What it does | Key needed |
|---|---|---|
GET /me |
Your business name, its ArtworkDesk address, your plan and currency. Handy for checking a key works. | Any |
GET /orders |
Orders, newest first. | Any |
GET /orders/{number} |
One order, by job number (such as JOB-1001) or its id. |
Any |
POST /orders |
Add an order. | Can add |
GET /customers |
Customers, newest first. | Any |
GET /customers/{id} |
One customer, by id. |
Any |
POST /customers |
Add a customer. | Can add |
GET /quotes |
Quotes, newest first. | Any |
GET /quotes/{number} |
One quote, by quote number (such as Q-1001) or its id. |
Any |
GET /products |
Products that are in use, by name. | Any |
Filtering lists
| Endpoint | Query parameters |
|---|---|
GET /orders |
status: orders with at least one product line at this status (see below). reference: the website or other system's order reference. updated_since: orders changed since this date or time. |
GET /customers |
email: the customer with this email address (any capitals). updated_since. |
GET /quotes |
status: draft, sent, accepted, declined, expired or approved. updated_since. |
GET /products |
None apart from paging. |
Use an ISO 8601 date or date and time for updated_since, for example 2026-10-01 or 2026-10-01T09:00:00Z.
Product line statuses:
status |
Shown in ArtworkDesk as |
|---|---|
awaiting_artwork |
Awaiting artwork |
artwork_received |
Mockup to do |
file_check |
File to check |
proof_sent |
Awaiting approval |
changes_requested |
Changes requested |
approved |
Queued for production |
in_production |
In production |
ready |
Ready to dispatch |
dispatched |
Dispatched |
cancelled |
Cancelled |
An order's own status is the least advanced status of its lines that aren't cancelled. See What each status means.
Paging
Lists come 50 at a time. Add per_page (1 to 100) and page to change this. Each list looks like this:
{
"data": [ ... ],
"meta": {
"page": 1,
"per_page": 50,
"total": 132,
"next": "https://yourbusiness.artworkdesk.com/api/v1/orders?page=2"
}
}
next is the address of the next page, or null on the last page. A single order, customer or quote comes back as {"data": { ... }}.
Adding an order
POST /orders with a JSON body:
{
"reference": "WEB-5521",
"customer": {
"email": "[email protected]",
"name": "Sam Patel",
"company": "Riverside FC",
"phone": "07700 900123"
},
"delivery_address": "12 High Street\nHarlow\nCM20 1AA",
"delivery_method": "Royal Mail Tracked 48",
"due_date": "2026-11-20",
"rush": false,
"request_artwork": true,
"items": [
{ "product_code": "TS100", "colour": "Navy", "size": "M", "quantity": 10, "decoration": "Screen print", "print_positions": "Front" },
{ "product_code": "TS100", "colour": "Navy", "size": "L", "quantity": 15, "decoration": "Screen print", "print_positions": "Front" }
]
}
| Field | Rules | What it does |
|---|---|---|
reference |
Optional, up to 120 characters. | Your own order number. If an order added through the API already has this reference, that order comes back instead of a new one being made. |
customer.email |
Required. | The customer is found by email address (any capitals), or added if they're new. An existing customer's details aren't changed. |
customer.name |
Required, up to 150. | Used when the customer is added. |
customer.company |
Optional, up to 150. | |
customer.phone |
Optional, up to 40. | |
delivery_address |
Optional, up to 2000. Use \n between lines. |
Left out, the customer's delivery address is used, or their billing address. |
delivery_method |
Optional, up to 120. | |
notes |
Optional, up to 5000. | Shown to the customer at the top of their order page, as Message shown to the customer. Don't put anything private here. |
due_date |
Optional date. | Left out, it's worked out from your lead times, as for any new order. |
rush |
Optional, true or false. |
Marks it as a rush order. |
request_artwork |
Optional, true or false. |
true emails the customer asking for their design, if any line is waiting for artwork. |
items |
Required, 1 to 100 items. | The products. |
items[].product_code |
Optional, up to 80. | Matched against your product codes (any capitals). A code that starts with one of yours followed by a hyphen, such as TS100-NAVY-M, matches TS100. |
items[].description |
Required if there's no product_code, up to 255. |
Matched against your product names when there's no code, or the code doesn't match. Otherwise it becomes the line's description. |
items[].quantity |
Required, 1 to 1,000,000. | |
items[].colour |
Optional, up to 80. | |
items[].size |
Optional, up to 20. | Such as M. |
items[].decoration |
Optional, up to 120. | Left out, the product's usual decoration is used. |
items[].print_positions |
Optional, up to 255. | |
items[].instructions |
Optional, up to 3000. | Instructions for the team. |
items[].needs_mockup |
Optional, true or false. |
Left out, the product's or your usual setting is used. |
Items for the same product, colour, decoration, print positions and instructions are put on one line, with their sizes. So send one item for each size, as in the example, and you get one line of 25 with 10 M and 15 L.
The order's Came from is Another system (API), and your team gets the usual "new order from your website" notification.
It answers 201 with the new order, or 200 with the existing one (and a message) if the reference was already used.
Adding a customer
POST /customers with a JSON body:
{
"name": "Sam Patel",
"company": "Riverside FC",
"email": "[email protected]",
"phone": "07700 900123",
"billing_address": "12 High Street\nHarlow\nCM20 1AA",
"delivery_address": null,
"notes": "Club secretary"
}
name (up to 150) and email are required. company (150), phone (40), billing_address, delivery_address (2000 each) and notes (5000) are optional.
It answers 201 with the new customer. If a customer with that email address already exists, it answers 200 with that customer and a message, and doesn't change them.
Errors
| Code | Means |
|---|---|
401 |
No key, or the key isn't right. |
403 |
The account isn't active, your plan doesn't include the API, or a read-only key tried to add something. |
404 |
That order, quote or customer doesn't exist. |
422 |
Something in the body isn't right. errors says which field and why. |
429 |
Too many requests. Wait and try again. |
Errors come back with a message:
{
"message": "The customer.email field is required.",
"errors": { "customer.email": ["The customer.email field is required."] }
}
Rate limit
You can make up to 120 requests a minute. Over that, you get 429 until the minute is up.
What's sent back
The API and webhooks use the same shapes.
Order
{
"id": 4182,
"number": "JOB-1001",
"reference": "WEB-5521",
"source": "api",
"status": "awaiting_artwork",
"status_label": "Awaiting artwork",
"rush": false,
"due_date": "2026-11-20",
"delivery_method": "Royal Mail Tracked 48",
"delivery_address": "12 High Street\nHarlow\nCM20 1AA",
"customer": { ... },
"items": [ ... ],
"customer_link": "https://yourbusiness.artworkdesk.com/o/...",
"created_at": "2026-10-25T10:15:00+00:00",
"updated_at": "2026-10-25T10:15:00+00:00"
}
customer_link is the customer's own order page. source is where the order came from, such as api, shopify, woocommerce, etsy or quote.
Product line (item)
{
"id": 9921,
"description": "Heavyweight T-shirt",
"product_code": "TS100",
"quantity": 25,
"colour": "Navy",
"sizes": { "M": 10, "L": 15 },
"decoration": "Screen print",
"print_positions": "Front",
"instructions": null,
"status": "awaiting_artwork",
"status_label": "Awaiting artwork",
"department": null,
"stage": null
}
sizes is null when the line has no size breakdown. department and stage are filled in once the line is in production.
Customer
{
"id": 311,
"name": "Sam Patel",
"company": "Riverside FC",
"email": "[email protected]",
"phone": "07700 900123",
"billing_address": "12 High Street\nHarlow\nCM20 1AA",
"delivery_address": null,
"custom": [ { "label": "Account number", "type": "text", "value": "RFC-01" } ],
"created_at": "2026-10-25T10:15:00+00:00"
}
custom holds your own custom fields that have a value. email is null for customers added without one, for example from a shop order with no email.
Quote
{
"id": 77,
"number": "Q-1001",
"title": "Quote request",
"status": "draft",
"status_label": "Draft",
"subtotal": 250.0,
"vat": 50.0,
"total": 300.0,
"valid_until": "2026-11-24",
"customer": { ... },
"items": [
{ "description": "Heavyweight T-shirt", "kind": "product", "quantity": 25, "unit_price": 8.5, "discount_percent": 0, "vat_rate": 20 },
{ "description": "Setup: Heavyweight T-shirt", "kind": "charge", "quantity": 1, "unit_price": 37.5, "discount_percent": 0, "vat_rate": 20 }
],
"order_id": null,
"created_at": "2026-10-25T10:15:00+00:00",
"updated_at": "2026-10-25T10:15:00+00:00"
}
order_id is the order made from the quote, once there is one.
Product
{ "id": 12, "name": "Heavyweight T-shirt", "product_code": "TS100", "category": "Clothing", "decoration": "Screen print", "lead_days": 7, "needs_mockup": true }
Webhook messages
Each webhook is a POST with a JSON body like this:
{
"id": "6f1c3a52-8f0e-4a8e-9a7d-2b1f0c9e4d11",
"event": "order.dispatched",
"created_at": "2026-10-25T15:42:10+00:00",
"business": { "name": "Northside Print", "slug": "northside" },
"data": { ... }
}
id is different for every message, so you can spot one you've already handled.
event |
When | data holds |
|---|---|---|
order.created |
An order is added, however it's added. | order |
item.approved |
A product line is approved for production. | order_number, item, order |
item.ready |
A product line is ready to dispatch. | order_number, item, order |
order.dispatched |
An order, or part of it, is dispatched. | order, and shipment with carrier, tracking_number, tracking_link, shipped_on and items (the ids of the lines sent) |
quote.accepted |
A customer accepts a quote. | quote |
quote.approved |
A quote is approved. | quote |
quote.requested |
A quote request arrives from your website. See Quote requests from your website. | quote |
order.reorder_requested |
A customer asks to order again from their order page. | order_number, customer, lines (each with item_id, description, quantity and sizes) and note |
customer.created |
A customer is added, however they're added. | customer |
test |
You click Send a test. | message |
Headers
| Header | What it holds |
|---|---|
Content-Type |
application/json |
X-ArtworkDesk-Event |
The event, such as order.dispatched. |
X-ArtworkDesk-Signature |
sha256= followed by the HMAC-SHA256 of the body, using the webhook's signing secret. |
Answer with any 2xx code within 8 seconds. Anything else, or no answer, counts as a failure.
Checking the signature
Work out the HMAC-SHA256 of the raw body, exactly as it arrived, with your signing secret (it starts with whsec_). Compare it with the header, and ignore the message if they don't match.
PHP:
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('ARTWORKDESK_SECRET'));
$given = $_SERVER['HTTP_X_ARTWORKDESK_SIGNATURE'] ?? '';
if (! hash_equals($expected, $given)) {
http_response_code(401);
exit;
}
$message = json_decode($body, true);
// $message['event'], $message['data'] ...
http_response_code(200);
Node (Express):
const crypto = require('crypto');
app.post('/artworkdesk', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.ARTWORKDESK_SECRET)
.update(req.body)
.digest('hex');
const given = req.get('X-ArtworkDesk-Signature') || '';
if (given.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
return res.sendStatus(401);
}
const message = JSON.parse(req.body);
// message.event, message.data ...
res.sendStatus(200);
});
Using Zapier
When something happens in ArtworkDesk
- In Zapier, make a Zap and choose Webhooks by Zapier as the trigger, with the event Catch Hook.
- Copy the webhook address Zapier gives you.
- In ArtworkDesk, paste it into Send to under Webhooks, tick the events you want and click Add webhook.
- Make the event happen once, for example add a test customer for
customer.created, so Zapier can see a real message with all its fields. Send a test only checks the connection: its message has no order or customer in it. - Carry on with the Zap's actions in Zapier.
Adding orders or customers from Zapier
- In ArtworkDesk, make a key with Can add orders and customers ticked.
- In Zapier, add a Webhooks by Zapier action with the event Custom Request.
- Set the method to POST and the address to your base address followed by
/ordersor/customers. - In Data, write the JSON body, as in the examples above, using fields from earlier steps.
- Add the headers
Authorization(withBearerand your key),Content-Type(application/json) andAccept(application/json).
Make and other tools work the same way: a webhook address to receive events, and an HTTP request with your key to add things.
Still need help? Email [email protected] with your business's address and what you were trying to do.