Documents Webhooks
Documents webhooks push a shipment's documents to your own system as the carrier produces them, so you don't have to poll. Whenever the documents of freight you booked through POST /dispatching/jobs change, Shipwell sends the shipment's full, current document collection to your URL, signed, with a download link on each file.
They use the same webhook as tracking webhooks: one URL and one signing secret per company. You choose which events that webhook receives, tracking.updated, documents.updated or both.
Each delivery carries the same documents and statuses that GET /documents/{resource_id} returns under data, in the webhook's own, slimmer shape: files never carry content inline, a file's content can be null, and each document adds a base64_link. Don't validate deliveries with your GET response schema; the webhook's is DocumentCollectionSummary in the API reference. Everything on the Documents page about statuses and document types applies here too.
How it works
-
Subscribe, once.
Register your URL with
documents.updatedinevent_names, or tick Document updates on the webhook you already have. - Send a test, once. The same test as for tracking proves Shipwell reaches your endpoint and your signature check passes.
-
Receive updates.
Book freight as usual. Each time its documents change, a signed
documents.updatedevent arrives at your URL, usually within a minute or two. - Download the files. Each file carries a fresh download link. Fetch it as soon as the event arrives; you make no API call.
-
Pull when you need to.
If a link is missing or has expired, the document's
base64_linkreturns its files inline.GET /documents/{resource_id}returns the current collection at any time.
Before you start
-
Documents webhooks must be enabled for your company
, in addition to tracking webhooks. Until they are, asking for
documents.updatedanswers403withdocuments_webhooks_not_enabled. Contact your Shipwell representative to turn them on. -
Everything else is as for tracking webhooks.
Your endpoint must be public HTTPS, answer any
2xxwithin 10 seconds, and verify every delivery's signature. See Before you start on the Tracking Webhooks page.
Subscribe
In the Shipwell app
Open Company → Webhooks. In the Tracking section, choose Edit on your webhook, or Set up tracking webhook if you don't have one yet. Under Events, tick Document updates, alongside Tracking updates or on its own, then save. At least one event must stay ticked.
The webhook's details then list both events, and Delivery history shows which event each delivery carried in its Event column.
Through the REST API
Include documents.updated in event_names when you register your URL. Everything else about registering a URL is unchanged.
Example request
curl --request POST 'https://sandbox-api.shipwell.com/tracking/webhooks' \
--header 'Authorization: Token <user-token>' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://tms.example.com/hooks/shipwell",
"event_names": ["tracking.updated", "documents.updated"],
"error_contact_email": "integrations@example.com",
"custom_data": { "tms": "acme-prod" }
}'The response is the same as for tracking, with "event_names": ["tracking.updated", "documents.updated"]. Store the secret now: it is returned only once.
A company has one webhook, so registering again answers 409. To add documents to a webhook you already registered, use the app.
Receive documents.updated
Shipwell sends documents.updated whenever the shipment's document collection changes:
- when document retrieval starts after the booking,
- when a document arrives or finishes,
- when retrieval completes or fails.
If a check with the carrier changes nothing, nothing is sent. The headers and signature are exactly those of tracking deliveries.
Example documents.updated (abridged to one document)
{
"id": "01KC3B9R2T4V6X8Z0A2C4E6G8J",
"event_name": "documents.updated",
"occurred_at": "2026-08-23T17:05:00.031882+00:00",
"webhook_id": "01KC2W7HQ3M8R5T6V9X0YZABCD",
"source": {
"publishing_system": "genesis",
"company_id": "3b1f6c2e-8a4d-4f7b-9c21-5e7d0a9b4c11",
"environment": "sandbox"
},
"custom_data": { "tms": "acme-prod" },
"sequence": 3,
"details": {
"resource_id": "9f2c1b84-6d3a-4c17-9b0e-2f5a7c48d31e",
"documents": {
"resource_id": "9f2c1b84-6d3a-4c17-9b0e-2f5a7c48d31e",
"resource_type": "document_collection",
"self_link": "https://sandbox-api.shipwell.com/documents/9f2c1b84-6d3a-4c17-9b0e-2f5a7c48d31e",
"status": "COMPLETED",
"is_terminal": true,
"documents": [
{
"id": "01KBZD1P4M0000000000000000",
"resource_type": "document",
"document_type": "BOL",
"name": "Bill of Lading",
"status": "COMPLETED",
"completed_at": "2026-08-23T17:04:18.220Z",
"self_link": "https://sandbox-api.shipwell.com/documents/9f2c1b84-6d3a-4c17-9b0e-2f5a7c48d31e/BOL",
"base64_link": "https://sandbox-api.shipwell.com/documents/9f2c1b84-6d3a-4c17-9b0e-2f5a7c48d31e/BOL?encoding=base64",
"files": [
{
"id": "01KBZD1P4N0000000000000000",
"format": "PDF",
"filename": "bol-77120.pdf",
"size_in_bytes": 184223,
"content": {
"url": { "value": "https://documents.example.com/bol-77120.pdf?X-Amz-Expires=600&X-Amz-Signature=..." },
"base64": null
}
}
]
}
],
"errors": []
}
}
}| Field | Meaning |
|---|---|
id, occurred_at, webhook_id, source, custom_data |
As for tracking.updated. |
event_name |
documents.updated. |
sequence |
Increases for each resource_id, counted separately from tracking.updated. A shipment's tracking and documents each have their own sequence. |
details.documents |
The full collection, with every document so far rather than only the new ones. Same documents and statuses as GET /documents/{resource_id}, in the webhook's shape (DocumentCollectionSummary). |
documents[].self_link |
The document's own endpoint, GET /documents/{resource_id}/{document_type}. |
documents[].base64_link |
The same endpoint with ?encoding=base64. Call it with your API key to get the files inline. It returns every document of that type, so pick the one whose id matches. |
files[].content.url.value |
A download link set when this delivery was sent. See Download the files. |
files[].content |
null when Shipwell has no link for the file, for example a file it holds only inline. Use base64_link. |
Download the files
The webhook never carries file content, so its size stays small whatever the documents weigh. Each file carries a link instead:
- A Shipwell link , valid for about 10 minutes from when that delivery was sent, or
- the carrier's own link , which expires on the carrier's schedule.
Download each file as soon as the event arrives, before you do anything slower. A retry of the same event is sent with fresh links.
If a file has content: null, or its link has expired or fails, call the document's base64_link with your API key. It returns the files inline as base64 for every document of that document_type, so a shipment with two BOLs returns both: pick the document whose id matches, and call the link once per type rather than once per document. A file over 50 MB comes back with too_large_for_inline: true and a link instead. See Documents for the response.
Handle each delivery
Use the same four rules as for tracking deliveries: ignore an id you have already processed; keep the highest sequence per resource_id and event_name and ignore anything lower; replace your copy of the collection rather than merging; store, acknowledge, then process.
Two things are specific to documents:
-
Keep sequences per event.
Track the highest
sequenceperresource_idandevent_name. Adocuments.updatedwithsequence: 2is not older than atracking.updatedwithsequence: 7. -
A finished collection can reopen.
When Shipwell retrieves a shipment's documents again, a later delivery can go back from
is_terminal: truetofalse. Apply it bysequencelike any other. This is unlike tracking, where a finished shipment never changes again.
GET /documents/{resource_id} carries no sequence, so it can't be ordered against deliveries: a delivery that was being retried while you pulled may hold an older collection than the one you pulled. Use the pull to fill gaps, such as catching up after downtime, keep applying deliveries by sequence, and pull that shipment again after applying any delivery that arrives after a pull. A retry can arrive hours later.
Retries, pausing and delivery history
These work as for tracking webhooks, because it is the same webhook: see Retries and Pause, change or remove a webhook. Pausing the webhook pauses both events.
One difference: a retry of a documents.updated event keeps its id and sequence, but its file links are made fresh for that attempt, so the body changes. Recognise a retry by its id, never by comparing bodies, and always download from the links in the attempt you received, not from an earlier one.
Errors
| Status | code |
Meaning |
|---|---|---|
403 |
documents_webhooks_not_enabled |
event_names includes documents.updated, and documents webhooks aren't enabled for your company yet. Contact your Shipwell representative, or leave it out of event_names. |
403 |
webhooks_not_enabled |
Tracking webhooks, which documents webhooks ride on, aren't enabled for your company yet. |
Every other error is as listed for tracking webhooks.
Troubleshooting
| Symptom | Check |
|---|---|
Tracking arrives but no documents.updated |
That the webhook's events include documents.updated (the details in Company → Webhooks list them), and that the shipment's documents have changed since you subscribed. GET /documents/{resource_id} shows the current collection. |
A download link answers 403 or AccessDenied |
It has expired. Shipwell links last about 10 minutes, so download on receipt, or call the document's base64_link. |
A file has content: null |
Shipwell holds that file without a link. Call the document's base64_link. |
| Documents look older than what you already have | You applied a lower sequence, or compared against the tracking sequence. Keep the highest sequence per resource_id and event_name. |
API reference
Tracking Webhooks API reference: the documents.updated payload with every field, under Webhooks.
Next steps
Read Documents for what the collection's fields and statuses mean, and Tracking Webhooks for verifying signatures, with Node.js and Python examples.