Introduction
The Xtatee API lets your business create shipments, read their status and receive updates automatically, without anyone typing into our dashboard. It is built for businesses that already keep their own orders in a database: you send us the shipment, we move it, and every change we make flows back to you.
Ship from our China warehouse to Nigeria (collected from our pickup hub, or delivered to the door) or door to door to other countries such as the UK, Europe and the US. See International shipping.
Shipments created through the API are kept separately from Xtatee's own customers' shipments. Our team is notified the moment you create one, handles it like any other, and updates its status as it moves. Each update reaches you in three ways: through the API, by email, and by webhook.
1. Get a key
Sign up for an Xtatee account and apply as a business. After we approve you and you pay the first month, your key appears straight away.
2. Send shipments
POST the items, receiver and route.
3. Stay in sync
We call your server on every update.
API access is part of the Xtatee partner plan: โฆ75,000 per month, paid online from your dashboard. Sign up as a business; every application is approved by our team.
- check_circlePartner rates
Wholesale freight rates, lower than our public prices, shown to you inside your dashboard. - check_circleDeveloper API
Create and track shipments from your own system with a simple API. - check_circleAutomatic updates
Every status change reaches your system by webhook and your customers by email. - check_circleYour own dashboard
See your shipments, invoices and weekly targets in one place. - check_circleFull China-side service
Receiving, consolidation, repacking and labelling under your own brand at our Guangzhou warehouse. - check_circle21 days free storage
Goods wait at our warehouse for 21 days at no charge. - check_circleDirect communication
A direct line to our team, not a ticket queue. - check_circleDoor-to-door options
Europe, the US and other destinations, quoted per destination.
If a renewal is not paid: you keep your key for a 7-day grace period after the due date. After that, creating new shipments is paused (403 subscription_lapsed). Shipments already in progress keep syncing, reading and tracking still work, nothing is deleted, and paying restores everything at once.
Authentication
Every request must carry your API key as a Bearer token. Find it in your dashboard under Developer API.
Authorization: Bearer YOUR_API_KEY403 account_suspended.Requests & responses
- Send and receive JSON. Set
Content-Type: application/jsonon requests with a body. - Weights are in kilograms, volume in cubic metres (CBM). The API carries no prices (see Prices & billing).
- Timestamps are ISO 8601 in UTC, for example
2026-10-10T09:41:12+00:00. - Limit: 120 requests per minute per key. Over it you get
429with aRetry-Afterheader.
A successful response looks like this:
{
"status": "success",
"message": "Optional human readable note",
"data": {
"...": "..."
}
}A failed one always has a machine readable code and, for validation problems, the exact fields at fault:
{
"status": "error",
"code": "validation_failed",
"message": "Some fields are missing or invalid.",
"errors": {
"receiver.phone": "Required, up to 40 characters.",
"items[0].quantity": "A whole number, 1 or more."
}
}Quickstart
Check that your key works. It returns your business name and webhook address.
/me{
"status": "success",
"data": {
"business": "Ada's Gadget Store",
"email": "ada@example.com",
"status": "active",
"webhook_url": null,
"subscription": {
"status": "active",
"paid_until": "2026-11-10T09:41:12+00:00",
"can_create_shipments": true
},
"rate_limit": {
"requests_per_minute": 120
}
}
}Then create your first shipment (see Create a shipment) and set a webhook address in your dashboard so updates start flowing back.
Account
List locations and options
/locationsReturns the values you may use for origin, destination and shipping_method, the two sides, every status a shipment can have on each side, the International methods and goods types, and the items we do not ship (prohibited_items). Call it once and cache it. The destination of a Nigeria shipment must be a location in Nigeria.
{
"status": "success",
"data": {
"locations": [
{
"name": "China Warehouse",
"country": "China",
"doorstep": false
},
{
"name": "Lagos Pickup Hub",
"country": "Nigeria",
"doorstep": false
}
],
"shipping_methods": [
{
"value": "Normal",
"label": "Normal cargo"
},
{
"value": "Express",
"label": "Express cargo"
},
{
"value": "Gadget",
"label": "Gadgets / electronics"
},
{
"value": "HK/Liquid/Battery",
"label": "HK special cargo: liquids, batteries, powders"
},
{
"value": "Sea Shipping",
"label": "Sea (Normal Goods) - China to Lagos"
},
{
"value": "Sea Battery",
"label": "Sea (Pure Battery Goods - Powerbanks, Lithium Batteries etc) - China to Lagos"
}
],
"sides": [
"nigeria",
"international"
],
"statuses": [
"Pending",
"Partially Received",
"Received in China",
"In Transit",
"Arrived at Pickup Hub",
"Delivered",
"Cancelled"
],
"international_statuses": [
"Pending",
"Partially Received",
"Received in China",
"In Transit",
"Delivered",
"Cancelled"
],
"international_shipping_methods": [
{
"value": "Air",
"label": "Air freight"
},
{
"value": "Sea",
"label": "Sea freight"
}
],
"goods_types": [
{
"value": "non_copy",
"label": "Non-copy goods",
"description": "Original or unbranded items"
},
{
"value": "copy",
"label": "Copy goods",
"description": "Replicas of brand-name products"
}
],
"prohibited_items": [
"Gadgets (Phones, Tablets, Laptops etc) to Europe, USA, South Africa etc",
"Weapons, firearms, ammunition, and anything that looks like a weapon (including imitation guns and knives)",
"Explosives and fireworks",
"Illegal drugs, narcotics, and controlled or prescription medicines"
]
}
}(prohibited_items shortened here: the response has the whole list.)
List International destinations
/countriesThe countries you can ship to door to door, for destination_country. Each one says whether the receiver pays import duty on arrival, the extra items that country does not accept (restricted_items, on top of the general prohibited_items), and the services you can use there: each shipping_method and goods_type with its minimum chargeable weight and usual transit time. quote_required: true means we have not published a rate for that country yet: you can still create the shipment, and our team prices it by hand. No prices are returned (they are in your dashboard). The list changes rarely; cache it for a day.
{
"status": "success",
"data": [
{
"name": "United Kingdom",
"duty_on_arrival": false,
"restricted_goods_note": null,
"restricted_items": [],
"quote_required": false,
"services": [
{
"shipping_method": "Air",
"goods_type": "non_copy",
"min_weight_kg": 20,
"transit_time": "8 - 11 days"
},
{
"shipping_method": "Air",
"goods_type": "copy",
"min_weight_kg": 20,
"transit_time": "8 - 11 days"
},
{
"shipping_method": "Sea",
"goods_type": "non_copy",
"min_weight_kg": 101,
"transit_time": "50 - 60 days"
},
{
"shipping_method": "Sea",
"goods_type": "copy",
"min_weight_kg": 101,
"transit_time": "50 - 60 days"
}
]
}
]
}Create a shipment
/shipmentsCreates a shipment and tells Xtatee about it. Send everything we need to receive, process and deliver it. You get back the shipment with its tracking number: keep it next to your own order.
| Field | Type | Required | Description |
|---|---|---|---|
| reference | string | No | Your own order id (max 100 characters). Strongly recommended: sending the same reference again returns the existing shipment instead of creating a duplicate, so retrying after a timeout is safe. |
| shipping_method | string | Yes | One shipping method for the whole shipment and every item in it. Nigeria: Normal, Express, Gadget, HK/Liquid/Battery, Sea Shipping, Sea Battery (not case sensitive); gadgets are priced per item (phone, tablet or laptop, set by our warehouse). International: Air or Sea, both priced per kg. Goods that need a different method go in a separate shipment. |
| goods_type | string | No | International shipments only, and required for them: non_copy (original or unbranded items) or copy (replicas of brand-name products). |
| confirm_no_prohibited | boolean | No | International shipments only, and required for them: true, confirming the shipment holds none of the prohibited_items (/locations) or the country's restricted_items (/countries). |
| side | string | No | nigeria (default) or international. Sending destination_country without side also means International. See International shipping. |
| origin | string | Yes | Where we collect the goods. A location name from /locations, for example China Warehouse (not case sensitive). |
| destination | string | Yes | Nigeria shipments: where the goods go, a location in Nigeria from /locations. Not needed for International shipments. |
| destination_country | string | No | International shipments only, and required for them: a country name from /countries (not case sensitive). |
| last_mile_address | string | No | Street address for delivery to the door (max 500 characters). Nigeria: leave out to collect from the pickup hub. International: required, the full delivery address in the destination country. |
| receiver.name | string | Yes | Who receives the goods. |
| receiver.phone | string | Yes | Receiver's phone number, for the courier. |
| receiver.email | string | No | Receiver's email address. |
| items | array | Yes | Between 1 and 50 line items, described below. |
| items[].description | string | Yes | What it is, up to 255 characters. Be specific: customs and inspection depend on it. |
| items[].quantity | integer | Yes | Number of units, 1 or more. |
| items[].shipping_option | string | No | Deprecated: send shipping_method once instead. Still accepted when every item names the same method; items that name different methods are refused with 422. |
| items[].weight_kg | number | No | Declared weight of the whole line (all units), in kg. Used for the estimate. |
| items[].cbm | number | No | Declared volume of the whole line in CBM. |
| items[].local_tracking_no | string | No | The waybill or tracking number of the parcel arriving at our warehouse. |
| items[].repack | boolean | No | Ask us to repack the item (charged separately). |
| items[].inspect | boolean | No | Ask us to inspect the contents (charged separately). |
| notes | string | No | Anything our team should know (max 1000 characters). |
shipping_method once for the whole shipment. Items no longer carry their own method, and responses and webhooks show it on the shipment. An older integration that sends shipping_option on each item keeps working as long as every item names the same method.Response, 201 Created:
{
"status": "success",
"message": "Shipment created. Xtatee has been notified.",
"data": {
"tracking_number": "XB-4A7DC5DA",
"reference": "ORDER-1001",
"status": "Pending",
"display_status": "Pending",
"side": "nigeria",
"shipping_method": "Normal",
"goods_type": null,
"prohibited_items_confirmed_at": null,
"origin": "China Warehouse",
"destination": "Lagos Pickup Hub",
"destination_country": null,
"last_mile_address": "12 Allen Avenue, Ikeja, Lagos",
"receiver": {
"name": "Ada Obi",
"phone": "08012345678",
"email": "ada@example.com"
},
"delivery": null,
"measured": {
"weight_kg": 0,
"cbm": 0,
"repack_weight_kg": null
},
"notes": "Handle with care",
"sent_out_at": null,
"pickup_hub_at": null,
"created_at": "2026-10-10T09:41:12+00:00",
"updated_at": "2026-10-10T09:41:12+00:00",
"items": [
{
"description": "50x iPhone cases",
"quantity": 50,
"local_tracking_no": "SF1234567",
"received_at": "2026-10-11T10:20:00+00:00",
"repack": true,
"inspect": false,
"declared": {
"weight_kg": 12.5,
"cbm": 0.08000000000000000166533453693773481063544750213623046875
},
"measured": {
"weight_kg": 12.699999999999999289457264239899814128875732421875,
"cbm": 0.070000000000000006661338147750939242541790008544921875
}
},
{
"description": "Phone stands",
"quantity": 20,
"local_tracking_no": null,
"received_at": null,
"repack": false,
"inspect": true,
"declared": {
"weight_kg": 3,
"cbm": null
},
"measured": {
"weight_kg": 3,
"cbm": 0.0200000000000000004163336342344337026588618755340576171875
}
}
],
"history": [
{
"status": "Pending",
"note": "Shipment created through the API.",
"at": "2026-10-10T09:41:12+00:00"
}
]
}
}reference. You will get 200 with "idempotent_replay": true and the original shipment, never a second one.International shipping
Besides Nigeria, we ship door to door from our China warehouse to other countries. An International shipment is created with the same endpoint and the same items; these are different in the request:
"side": "international",destination_country: a name fromGET /countriesinstead of adestinationlocation,last_mile_addressis required: the full delivery address in that country,shipping_methodisAirorSea, andgoods_typeisnon_copyorcopy(both required; the country'sservicesin/countriessay which it has),"confirm_no_prohibited": trueis required: the shipment holds none of theprohibited_itemsfrom/locationsor the country'srestricted_items.
"shipping_method": "Air" or "Sea", a goods_type and "confirm_no_prohibited": true. Both methods are priced per kg at your partner price for the country, with a minimum chargeable weight per country (in services[].min_weight_kg): a lighter shipment is billed at the minimum. Nigeria shipments are unchanged.How an International shipment differs once it is created:
| Topic | International shipments |
|---|---|
| Journey | Pending, Partially Received, Received in China, In Transit, then Delivered. There is no pickup hub, so Arrived at Pickup Hub never happens and pickup_hub_at stays null. |
| Delivered | Means delivered to the receiver's door, so display_status is Delivered too (for Nigeria it reads "Available for pickup"). |
| delivery | When the goods are handed to the delivery company in the destination country, delivery.company and delivery.tracking_number are filled in and you receive the shipment.delivery_updated webhook. Give that tracking number to your customer: the delivery company contacts them about duty. |
| Duty | delivery.duty_on_arrival: true means import duty and taxes are charged by the destination country's customs, paid by the receiver to customs or the delivery company. They are never part of what Xtatee charges. |
| Billing | In US dollars, per kg at your partner price for that country, method and goods type, with the country's minimum weight, plus China storage after the free days. No demurrage. You see and pay the invoice in your dashboard, as for Nigeria shipments. |
| Prohibited goods | We never carry the prohibited_items listed by /locations, and some countries refuse more (restricted_items in /countries). Check both before you accept an order: every International shipment must confirm it with confirm_no_prohibited. |
The response is the usual shipment object. Here is one that has been delivered:
{
"status": "success",
"data": {
"tracking_number": "XB-9C21E07B",
"reference": "ORDER-2001",
"status": "Delivered",
"display_status": "Delivered",
"side": "international",
"shipping_method": "Air",
"goods_type": "non_copy",
"prohibited_items_confirmed_at": "2026-10-10T09:41:12+00:00",
"origin": "China Warehouse",
"destination": "United Kingdom",
"destination_country": "United Kingdom",
"last_mile_address": "Flat 4, 21 Baker Street, London NW1 6XE",
"receiver": {
"name": "Tunde Bello",
"phone": "+447700900123",
"email": "tunde@example.com"
},
"delivery": {
"company": "DHL",
"tracking_number": "JD014600006281",
"duty_on_arrival": true
},
"measured": {
"weight_kg": 6.20000000000000017763568394002504646778106689453125,
"cbm": 0.0299999999999999988897769753748434595763683319091796875,
"repack_weight_kg": null
},
"notes": null,
"sent_out_at": "2026-10-12T14:03:55+00:00",
"pickup_hub_at": null,
"created_at": "2026-10-10T09:41:12+00:00",
"updated_at": "2026-10-12T14:03:55+00:00",
"items": [
{
"description": "Ankara fabric, 30 yards",
"quantity": 1,
"local_tracking_no": "YT889900112",
"received_at": "2026-10-11T10:20:00+00:00",
"repack": false,
"inspect": false,
"declared": {
"weight_kg": 6,
"cbm": null
},
"measured": {
"weight_kg": 6.20000000000000017763568394002504646778106689453125,
"cbm": 0.0299999999999999988897769753748434595763683319091796875
}
}
],
"history": [
{
"status": "Pending",
"note": "Shipment created through the API.",
"at": "2026-10-10T09:41:12+00:00"
},
{
"status": "In Transit",
"note": "Departed Guangzhou",
"at": "2026-10-12T14:03:55+00:00"
},
{
"status": "In Transit",
"note": "Handed to DHL, tracking number JD014600006281.",
"at": "2026-10-16T08:12:40+00:00"
},
{
"status": "Delivered",
"note": null,
"at": "2026-10-18T15:30:02+00:00"
}
]
}
}destination_country, a last_mile_address, Air or Sea, a goods_type or "confirm_no_prohibited": true gets 422 validation_failed with the field named in errors. So does a Nigeria shipment whose destination is outside Nigeria: send it as International instead.Retrieve & list
Retrieve one shipment
/shipments/{tracking_number}Returns the full shipment: items, measured weights and the complete status history.
curl https://xtatee.com/api/v1/shipments/XB-4A7DC5DA \
-H "Authorization: Bearer YOUR_API_KEY"{
"status": "success",
"data": {
"tracking_number": "XB-4A7DC5DA",
"reference": "ORDER-1001",
"status": "In Transit",
"display_status": "In Transit",
"side": "nigeria",
"shipping_method": "Normal",
"goods_type": null,
"prohibited_items_confirmed_at": null,
"origin": "China Warehouse",
"destination": "Lagos Pickup Hub",
"destination_country": null,
"last_mile_address": "12 Allen Avenue, Ikeja, Lagos",
"receiver": {
"name": "Ada Obi",
"phone": "08012345678",
"email": "ada@example.com"
},
"delivery": null,
"measured": {
"weight_kg": 15.699999999999999289457264239899814128875732421875,
"cbm": 0.0899999999999999966693309261245303787291049957275390625,
"repack_weight_kg": 14.9000000000000003552713678800500929355621337890625
},
"notes": "Handle with care",
"sent_out_at": "2026-10-12T14:03:55+00:00",
"pickup_hub_at": null,
"created_at": "2026-10-10T09:41:12+00:00",
"updated_at": "2026-10-12T14:03:55+00:00",
"items": [
{
"description": "50x iPhone cases",
"quantity": 50,
"local_tracking_no": "SF1234567",
"received_at": "2026-10-11T10:20:00+00:00",
"repack": true,
"inspect": false,
"declared": {
"weight_kg": 12.5,
"cbm": 0.08000000000000000166533453693773481063544750213623046875
},
"measured": {
"weight_kg": 12.699999999999999289457264239899814128875732421875,
"cbm": 0.070000000000000006661338147750939242541790008544921875
}
},
{
"description": "Phone stands",
"quantity": 20,
"local_tracking_no": null,
"received_at": null,
"repack": false,
"inspect": true,
"declared": {
"weight_kg": 3,
"cbm": null
},
"measured": {
"weight_kg": 3,
"cbm": 0.0200000000000000004163336342344337026588618755340576171875
}
}
],
"history": [
{
"status": "Pending",
"note": "Shipment created through the API.",
"at": "2026-10-10T09:41:12+00:00"
},
{
"status": "In Transit",
"note": "Departed Guangzhou on flight CZ399",
"at": "2026-10-12T14:03:55+00:00"
}
]
}
}List shipments
/shipmentsNewest first. Each row has the same fields as above without items and history; fetch one shipment for those.
| Field | Type | Required | Description |
|---|---|---|---|
| status | string | No | Only shipments in this status. |
| side | string | No | nigeria or international. |
| reference | string | No | Find the shipment for one of your order ids. |
| page | integer | No | Page number, starting at 1. |
| per_page | integer | No | 1 to 100, default 20. |
curl "https://xtatee.com/api/v1/shipments?status=In%20Transit&per_page=50" \
-H "Authorization: Bearer YOUR_API_KEY"{
"status": "success",
"data": [
"[ shipments ]"
],
"meta": {
"page": 1,
"per_page": 50,
"total": 132,
"total_pages": 3
}
}Cancel a shipment
/shipments/{tracking_number}/cancelCancels a shipment that is still Pending. Once we have started handling it, cancelling is no longer possible through the API and you get 409 cannot_cancel: please contact support.
curl -X POST https://xtatee.com/api/v1/shipments/XB-4A7DC5DA/cancel \
-H "Authorization: Bearer YOUR_API_KEY"The shipment object
The same object is returned by the API and sent inside webhooks, so what you store from a webhook always matches what you would read from the API.
| Field | Type | Required | Description |
|---|---|---|---|
| tracking_number | string | Yes | Xtatee's tracking number, like XB-4A7DC5DA. Unique and permanent. |
| reference | string|null | No | Your order id, if you sent one. |
| status | string | Yes | Current status, a stable code. See below. |
| display_status | string | Yes | What YOUR customers should read. The same as status, except that on a Nigeria shipment Delivered is shown as Available for pickup (see below). |
| side | string | Yes | nigeria or international. |
| origin / destination | string | Yes | The locations you chose. For an International shipment destination is the country. |
| destination_country | string|null | Yes | The International destination country, null for Nigeria. |
| shipping_method | string|null | Yes | The one method for the whole shipment: a Nigeria method, or Air / Sea for International. null only on an older shipment whose items had different methods. |
| goods_type | string|null | Yes | International only: non_copy or copy. null for Nigeria. |
| prohibited_items_confirmed_at | string|null | Yes | International only: when you confirmed the shipment holds no prohibited items, ISO 8601 UTC. |
| last_mile_address | string|null | No | Door delivery address, if any (always set for International). |
| receiver | object | Yes | name, phone, email. |
| delivery | object|null | Yes | International only (null for Nigeria): company and tracking_number of the delivery company in the destination country (null until it has the goods), and duty_on_arrival. |
| measured | object | Yes | weight_kg and cbm measured at our warehouse (both 0 until we weigh the shipment), and repack_weight_kg: the whole shipment's weight after repacking, which repacking is charged on (null until recorded, 0 when not repacked). |
| sent_out_at | string|null | Yes | When the shipment left China (first marked In Transit), ISO 8601 UTC. null until then. |
| pickup_hub_at | string|null | Yes | When it arrived at the pickup hub and we sent the pickup message, ISO 8601 UTC. null until then, and always null for International. |
| items[] | array | Yes | Each line with declared and measured weight / CBM (null until measured), received_at (the day the package reached our warehouse, or null), plus repack, inspect and local_tracking_no. |
| history[] | array | Yes | Every status the shipment has had, oldest first: status, note, at. |
| created_at / updated_at | string | Yes | ISO 8601, UTC. |
Statuses
A shipment normally moves from top to bottom. Our team sets each status as the goods progress and may add a note, which you receive with the update.
Partially Received means some of your items have reached our warehouse and others have not. When you create a shipment for goods that are already in our warehouse, it starts at Received in China (or Partially Received) instead of Pending. Arrived at Pickup Hub means the goods are ready to be collected. Cancelled is final.
International shipments skip the pickup hub:
Delivered, you have collected it from us, but your own customer may not have collected it from you yet. Show Available for pickup on your website for that status. The display_status field already does this for you; keep status for your own logic. An International shipment marked Delivered has reached the receiver's door.Prices & billing
What Xtatee charges you is never sent through the API or in a webhook, so nothing about our prices can end up on your own website. You add your own margin before billing your customer.
You see what we charge in your own Xtatee account instead: once your goods have been weighed we issue an invoice, you receive an email, and you can see and pay it from Developer API → Invoices in your dashboard (card or bank transfer through Paystack, which adds its transaction charge on its payment page).
Nigeria shipments are invoiced in naira on your partner rate card. International shipments are invoiced in US dollars at your partner price for the destination country, shown per country in your dashboard. Import duty in the destination country is never on our invoice.
Webhooks
Add a webhook address in your dashboard. Whenever we change one of your shipments we send a POST with the new state, so your own database can follow along without you polling. You also get an email for every update.
Events
| Event | When it is sent |
|---|---|
| shipment.status_updated | We changed the status (and optionally added a note). data.previous_status and data.note are included. |
| shipment.measured | We weighed and measured the goods. measured on the shipment and on each item is filled in. |
| shipment.delivery_updated | International only: the goods are with the delivery company in the destination country. delivery.company and delivery.tracking_number on the shipment are filled in. |
| shipment.method_updated | Our team set or changed the shipment's shipping_method (or, International, its goods_type). data.previous_method is included (null if there was none), and data.previous_goods_type on International shipments. |
| webhook.test | Sent when you press "Send test event" in your dashboard. |
Payload
{
"id": "evt_9f2c41d0a7b3e85c6d12",
"event": "shipment.status_updated",
"created_at": "2026-10-12T14:03:56+00:00",
"data": {
"shipment": {
"tracking_number": "XB-4A7DC5DA",
"reference": "ORDER-1001",
"status": "In Transit",
"...": "the full shipment object, as above"
},
"previous_status": "Pending",
"note": "Departed Guangzhou on flight CZ399"
}
}Headers
| Field | Type | Required | Description |
|---|---|---|---|
| X-Xtatee-Event | string | Yes | The event name. |
| X-Xtatee-Delivery | string | Yes | A unique id for this event. It is the same on every retry, so store it and ignore ones you have already processed. |
| X-Xtatee-Timestamp | integer | Yes | Unix time the request was signed. |
| X-Xtatee-Signature | string | Yes | sha256= followed by an HMAC-SHA256 of {timestamp}.{raw body} using your signing secret. |
Verify the signature
Anyone can post to a public URL, so check every webhook before you trust it. Compute the signature over the raw request body exactly as received, compare it in constant time, and reject requests whose timestamp is more than 5 minutes old. Your signing secret is in your dashboard.
Delivery & retries
- Answer with any 2xx status within 5 seconds. Do slow work after you respond.
- Anything else, or a timeout, counts as a failure. We retry after 1 min, 5 min, 30 min, 2 hours and 12 hours, then stop. Our team can also resend any delivery by hand.
- Events can arrive more than once or slightly out of order. Use
X-Xtatee-Deliveryto skip duplicates and the shipment'supdated_atto ignore stale updates. - Webhook addresses must use https and be reachable from the internet. We do not follow redirects.
Errors
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 401 | unauthorized / invalid_api_key | Missing, malformed or revoked key. |
| 403 | account_suspended | Your API account is suspended. Contact support. |
| 403 | subscription_required | Your first subscription payment has not been made yet. |
| 403 | subscription_lapsed | The subscription lapsed: new shipments are paused. Pay the renewal to resume. |
| 404 | not_found | No such shipment on your account, or unknown endpoint. |
| 409 | cannot_cancel | The shipment is past Pending. |
| 422 | validation_failed | A field is missing or invalid. See errors for which. |
| 429 | rate_limited | Over 120 requests a minute. Wait for Retry-After seconds. |
| 500 | server_error | Something went wrong on our side. Retry; with a reference it is safe. |
Go live checklist
- checkStore the API key and webhook secret in environment variables, never in code.
- checkSave our
tracking_numbernext to every order, and send your own order id asreference. - checkShipping abroad? Load
GET /countries, send"side": "international"with the country and the full address, and show your customer the delivery company's tracking number whenshipment.delivery_updatedarrives. - checkVerify the signature and timestamp on every webhook, and respond with a 2xx quickly.
- checkSkip webhook deliveries you have already handled (
X-Xtatee-Delivery). - checkHandle
422by showing theerrorsto whoever can fix the data, and429 / 5xxby retrying with backoff. - checkPress "Send test event" in your dashboard and confirm your endpoint answers 2xx.
Need help?
Tell us your business name and what you are trying to do. Contact us and we will help you get connected.