Skip to main content

Migrate from Windward

A client built for Windward's ocean tracking GraphQL API can talk to Dockflow by changing the endpoint URL and the credential value. Your queries, mutations and variables stay as they are.

note

Windward's endpoint URL and authentication are not public. The endpoint and the authentication below are Dockflow's own, not a copy of Windward's, so expect to change those two settings exactly as described. The first migration runs together with Dockflow and validates this surface. Contact [email protected] to plan it.

What to change​

SettingAfter
EndpointPOST https://api.dockflow.com/compat/windward/graphql
CredentialA Dockflow API key, in Authorization: Bearer <key> or in x-api-key: <key>

If your client calls publicAPIToken(clientId, clientSecret) first, keep that call and put the Dockflow API key in clientSecret. Dockflow verifies it and returns it as the token. clientId is ignored.

Get a token​

  1. Sign in to the API Console and open API Keys.
  2. Click Create Key, choose the Production environment and the Downstream scope.
  3. Copy the key. It is shown only once.

See Authorization for the key format and for revoking a key.

What is covered​

OperationWhat it does
publicAPITokenThe exchange above.
upsertTrackedShipmentsStarts tracking, or updates a shipment named by shipmentId or by the same identifiers. At most 500 items per call.
updateTrackedShipmentsMetadataUpdates the job number and the metadata. A job number already held by another shipment gives BAD_USER_INPUT.
deleteTrackedShipmentsStops tracking and removes the shipments. Returns false when an id is not found.
trackedShipmentsThe list, with limit (default 50, at most 500), offset, filterBy and searchTerm.
trackedShipmentsByIdsReads by id.
carriersThe carriers Dockflow knows, with an optional aliases filter.

Windward's vessel screening, company screening, detention and demurrage and early detection operations are not part of this surface. They fail GraphQL validation.

Errors​

Input and execution errors are in errors[] with HTTP 200. A filter that cannot be honoured gives BAD_USER_INPUT, because ignoring it would return the wrong rows. A refused credential keeps its Dockflow status (400, 401 or 403) with an errors[] body and extensions.code set to UNAUTHENTICATED or FORBIDDEN.

Identity​

  • One tracked object is one Dockflow tradeflow. Ids are 24 hex characters derived from the tradeflow. Ids that Windward issued do not resolve, so upsert your shipments once.
  • metadata.jobNumber is the tradeflow reference. Without one, Dockflow generates a reference from the container, else the bill of lading, else the booking. A job number given later renames that tradeflow.
  • A job number shared by several containers is one tradeflow with one id. A bill of lading that resolves to several containers is also one record. Such a record has containerNumber and container set to null. The upsert answer repeats what you sent.
  • A new upsert of a container without its earlier job number opens a second tradeflow.

Field mapping​

Input​

Windward fieldDockflow
containerNumberContainer reference.
bolBill of lading reference.
carrierBookingReferenceBooking reference.
scacCarrier SCAC.
metadata.jobNumberTradeflow reference.
metadata.pol, metadata.podPort of loading, port of discharge.
metadata.eta, metadata.etdEstimated arrival, estimated departure.
metadata.businessDataCustom references. They read back on GraphQL and are empty in webhook bodies.

customer, masterShipment and shipmentsUploadId are accepted and not used.

Events​

Dockflow codeWindward event
GEEQUIPMENT GTOT, EMPTY
GNEQUIPMENT GTIN, LADEN
AEEQUIPMENT LOAD, LADEN
VDTRANSPORT DEPA
VATRANSPORT ARRI
UVEQUIPMENT DISC, LADEN
GT, GDEQUIPMENT GTOT, LADEN
GREQUIPMENT GTIN, EMPTY

Other Dockflow events are left out. shipmentLocationTypeCode and the milestone type are PREPOL, POL, TSP, POD or POSTPOD, from the port roles of the plan. Timestamp codes are ACT, PLN and EST.

DateTime values are UTC in GraphQL records and local time with offset in webhook bodies.

What stays empty​

FieldValue
sharedShipmentLinknull.
status.id, status.delay.podETA, status.delay.reasonsnull or [].
vessel.flag, vessel.classnull.
response.latestShipmentDataFetch, response.latestSuccessfulDataFetchnull.
response.winningIdentifier.typenull.
route.geojsonnull, in webhook bodies.
shipmentEventTypeCode[].
metadata.eta, etd, pol, podnull on read. An upsert answer repeats what you sent.
carrierLatestStatus.status.typenull, in webhook bodies.
port.properties.areaType, secondaryLocodesnull or [].

response.message only takes DATA_AVAILABLE and NO_DATA_RECEIVED.

What behaves differently​

  • filterBy supports trackedShipment_id and shipment_container_number with one value. An empty condition is ignored. Other conditions give BAD_USER_INPUT.
  • searchTerm is an exact container number or job number.
  • sort and orderBy are accepted and ignored. The list is ordered by last change, oldest first.
  • A new shipment reads back by id at once. It enters the list when its import has created a shipment.
  • The showAISEvents filter is accepted and has no effect.

Webhooks​

Contact [email protected] to add a destination in the windward format. You receive its signing secret once. It cannot be read again.

Each delivery is one POST:

{
"eventTypes": ["CARRIER_ETA_UPDATE"],
"data": {
"shipment": { "identifiers": { "trackedShipmentId": "000000000000000000012345" } },
"metadata": { "jobNumber": "PO4564268", "businessData": [] }
}
}

The example shortens shipment. It also holds milestones, events, vessels, ports, predicted, carrierLatestStatus, delay and response.

Dockflow delivers once per change and sends the whole record, so the type is chosen from the record and is not a comparison with an earlier state. The first that applies wins: COMPLETED (tracking is inactive and has events), CARRIER_ATA_UPDATE, PREDICTED_ETA_UPDATE, CARRIER_ETA_UPDATE, CARRIER_VESSEL_UPDATE, CARRIER_STATUS_UPDATE, NEW_SHIPMENT.

Retries, timeouts and ordering follow Webhook delivery.

HeaderValue
X-Dockflow-Signaturesha256=<hex>: HMAC-SHA256 of the exact body with your signing secret.
X-Dockflow-TimestampUnix seconds. It is not signed, so it gives no replay protection.

Verify the signature​

Compute the HMAC over the exact bytes you received, before any JSON parsing:

import hashlib, hmac

def is_authentic(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)

How to test​

  1. Create a key with the UAT environment and the Downstream scope. It works on https://uat.dockflow.com/compat/windward/graphql and writes to your UAT organisation. A real number is really tracked there.
  2. Upsert a shipment:
curl -X POST https://uat.dockflow.com/compat/windward/graphql \
-H "x-api-key: uat_acme-logistics_Zk3n1c0Q2m9..." \
-H "Content-Type: application/json" \
-d '{"query":"mutation($s:[TrackedShipmentInput!]!){upsertTrackedShipments(shipments:$s){id}}","variables":{"s":[{"bol":"ANR00001010","scac":"MAEU","metadata":{"jobNumber":"PO4564268"}}]}}'
  1. Read it with trackedShipmentsByIds and the id from the answer. The import runs after the answer, so the first read can be short.
  2. To test a webhook, open the destination in the console and click Send test with the Live tradeflow scenario, naming the job number of a tradeflow that has data. The Ocean basic scenario is refused for this format.

Polling is also possible: GET /v3/upstream?format=windward with an Upstream key returns these records. See Upstream filters.

Was this page helpful?