Subscriptions are a way to get notified about the changes happening to your entities.

Creating a Subscription

{
  "name": "My Subscription",
  "url": "https://my.webhook.com",
  "eventTypes": [
    "shipmentUpdated"
  ]  
}

Will create POST messages to https://my.webhook.com for every shipment update, with the following example body:

{
  "type": "shipmentUpdated",
  "timestamp": "2023-09-14T07:03:30.714Z",
  "shipmentId": "S111222",
  "customerReference": "AAA123"
}
{
  "type": "shipmentUpdated",
  "timestamp": "2023-09-14T07:03:30.714Z",
  "shipmentId": "S111222",
  "customerReference": "AAA123"
}

This events typically contains:

  • The type of update (e.g. shipmentUpdated)
  • The timestamp of the update
  • The Forto shipment ID, which can be seen in the SHIP application (E.g. https://ship.forto.com/shipment/S111222/overview)
  • The customer reference sent by the customer for that shipment or booking request.
  • Additional details specific to events

Validating webhook requests

Every subscription has a signatureKey attribute which will be used to sign the requests we have. Webhook requests are sent with a x-hub-signature header. The signature can be calculated on the receiving end and be compared to the value of the header to make sure the request is sent from us.

Calculating the HMAC

Signature is calculated with creating the SHA1 hash of the raw JSON body as a string. Below, you can find example code snippets in several languages.

import * as crypto from 'crypto'

function generateSignature(signatureKey, payload) {
	return crypto.createHmac('sha1', signatureKey).update(Buffer.from(payload, 'utf-8')).digest('hex')
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import javax.xml.bind.DatatypeConverter;

public class HmacCalculator {
    public static String generateSignature(String signatureKey, String payload) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        Mac sha1Hmac = Mac.getInstance("HmacSHA1");
        SecretKeySpec secretKey = new SecretKeySpec(signatureKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
        sha1Hmac.init(secretKey);
        
        byte[] signatureBytes = sha1Hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        return DatatypeConverter.printHexBinary(signatureBytes).toLowerCase()
    }
}
import hmac
import hashlib

def create_hmac_sha1(signature_key, payload):
    signature = hmac.new(signature_key.encode('utf-8'), payload.encode('utf-8'), hashlib.sha1)
    return signature.hexdigest()

Event schemas

Below are the schemas for all the events being sent.

Shipment Created Event

NameTypeDescriptionValueConstraintsNotes
typestringShipment create event type"shipmentCreated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
customerReferencestringCustomer Reference of the related shipment---
{
  "type": "shipmentCreated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "customerReference": "AAA123"
}

Shipment Updated Event

NameTypeDescriptionValueConstraintsNotes
typestringShipment updated event type"shipmentUpdated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
customerReferencestringCustomer Reference of the related shipment---
{
  "type": "shipmentUpdated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "customerReference": "AAA123"
}

Shipment Deleted Event

NameTypeDescriptionValueConstraintsNotes
typestringShipment deleted event type"shipmentDeleted"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
customerReferencestringCustomer Reference of the related shipment---
{
  "type": "shipmentDeleted",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "customerReference": "AAA123"
}

Transport Plan Updated Event

NameTypeDescriptionValueConstraintsNotes
typestringTransport Plan Updated Event Type"transportPlanUpdated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
milestonestringMilestone that triggered the event"cargoReady", "pickup", "departure", "transshipmentArrival", "transshipmentDeparture", "transshipmentArrival2", "transshipmentDeparture2", "transshipmentArrival3", "transshipmentDeparture3", "transshipmentArrival4", "transshipmentDeparture4", "arrival", "delivery", "containerGateOut", "containerGateIn", "containerEmptyReturn", "containerEmptyPickup", "customsStopOrigin", "customsStopDestination", "containerDischargedTransshipment1", "containerDischargedTransshipment2", "containerDischargedTransshipment3", "containerDischargedTransshipment4", "containerLoadedTransshipment1", "containerLoadedTransshipment2", "containerLoadedTransshipment3", "containerLoadedTransshipment4", "transitAirportArrival", "transitAirportArrival2", "transitAirportArrival3", "transitAirportArrival4", "transitAirportDeparture", "transitAirportDeparture2", "transitAirportDeparture3", "transitAirportDeparture4", "containerGateInMultimodalTerminalOrigin", "containerGateOutMultimodalTerminalOrigin", "containerGateInMultimodalTerminalDestination", "containerGateOutMultimodalTerminalDestination", "containerDischargedDestination", "containerDepartureAtShipper", "verifiedGrossMarginStop", "departureFromPointOfReceipt", "containerLoadingOrigin", "containerEmptyDeparture", "containerDepartureDelivery", "arrivalPartner", "departurePartner", "arrivalOrigin", "receivedFromFlight"-required
isActualbooleanIs this milestone actually happened--required
isConfirmedbooleanIs this milestone confirmed--required
trackableIdstringTrackable ID of the container--required
containerNumberstringContainer number---
shipmentCustomerReferencestringCustomer Reference of the Shipment---
containerCustomerReferencestringCustomer Reference of the Container---
{
  "type": "transportPlanUpdated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "milestone": "cargoReady",
  "isActual": true,
  "isConfirmed": true,
  "trackableId": "64b786881185bb42abcdefgh",
  "containerNumber": "TEMU2341234",
  "shipmentCustomerReference": "AAA123",
  "containerCustomerReference": "CO123"
}

Customs Status Updated Event

NameTypeDescriptionValueConstraintsNotes
typestringCustoms Status Updated Event Type"customsStatusUpdated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
customerReferencestringCustomer Reference of the related shipment---
containersarrayTracking Events.--required
containers.idstringID of the container--required
containers.customerReferencestringReference for the container--required
containers.containerNumberstringContainer number--required
containers.statusstringLatest status given for the container--required
containers.descriptionstringHuman readable description for the customs status---
containers.newStatusbooleanDefined if this is a newly acquired information---
containers.customsReferenceNumberarray<array>Reference numbers of the container for customs---
containers.customsReferenceNumber (single item)array----
{
  "type": "customsStatusUpdated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "customerReference": "string",
  "containers": [
    {
      "id": "649d8d9306d6c4a111222a12",
      "customerReference": "AAA123",
      "containerNumber": "TEMU2341234",
      "status": "declaration-filed",
      "description": "Declaration filed",
      "newStatus": true,
      "customsReferenceNumber": [
        "AAA123",
        "BBB456"
      ]
    }
  ]
}

Document Updated Event

NameTypeDescriptionValueConstraintsNotes
typestringDocument Updated Event Type"documentUpdated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
actionstringWhat happened to the document"added", "updated", "deleted"-required
documentIdstringRelated Document ID--required
documentNamestringRelated Document Name--required
documentCreatedAtstringCreation date of the document-date-timerequired
documentTypestringDocument type--required
shipmentCustomerReferencestringCustomer Reference of the Shipment---
{
  "type": "documentUpdated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "action": "added",
  "documentId": "65030a3b2777d6c308181aac",
  "documentName": "document_name.pdf",
  "documentCreatedAt": "2019-08-24T14:15:22Z",
  "documentType": "shipmentInvoice",
  "shipmentCustomerReference": "AAA123"
}

Shipment Invoice Created Event

NameTypeDescriptionValueConstraintsNotes
typestringShipment Invoice Created Event Type"shipmentInvoiceCreated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringShipment ID---
invoiceNumberstring---required
invoiceTypestring-"creditNote", "freight", "customs", "additionalCharges"-required
statusstring-"Cancelled", "Outstanding", "Overdue", "Paid", "Cleared"-required
lineItemsarray---required
lineItems.descriptionstring---required
lineItems.costCodestring----
lineItems.quantitynumber-->= 0required
lineItems.vatClassstring---required
lineItems.exchangeRatenumberOnly exists if currencies are different->= 0-
lineItems.unitPriceobject---required
lineItems.unitPrice.amountnumber---required
lineItems.unitPrice.currencystring-"AED", "AUD", "BDT", "BGN", "BHD", "BND", "BRL", "CAD", "CHF", "CNY", "CYP", "CZK", "DKK", "EEK", "EUR", "FJD", "GBP", "HKD", "HRK", "HUF", "IDR", "ILS", "INR", "IRR", "ISK", "JOD", "JPY", "KRW", "KWD", "LTL", "LVL", "MAD", "MMK", "MTL", "MXN", "MYR", "NAD", "NOK", "NZD", "OMR", "PGK", "PHP", "PKR", "PLN", "QAR", "ROL", "RON", "RUB", "SAR", "SEK", "SGD", "SIT", "SKK", "THB", "TRL", "TRY", "TWD", "USD", "VND", "ZAR", "ZA"-required
lineItems.netAmountobject---required
lineItems.netAmount.amountnumber---required
lineItems.netAmount.currencystring-"AED", "AUD", "BDT", "BGN", "BHD", "BND", "BRL", "CAD", "CHF", "CNY", "CYP", "CZK", "DKK", "EEK", "EUR", "FJD", "GBP", "HKD", "HRK", "HUF", "IDR", "ILS", "INR", "IRR", "ISK", "JOD", "JPY", "KRW", "KWD", "LTL", "LVL", "MAD", "MMK", "MTL", "MXN", "MYR", "NAD", "NOK", "NZD", "OMR", "PGK", "PHP", "PKR", "PLN", "QAR", "ROL", "RON", "RUB", "SAR", "SEK", "SGD", "SIT", "SKK", "THB", "TRL", "TRY", "TWD", "USD", "VND", "ZAR", "ZA"-required
issueDatestring--date-timerequired
netAmountobject---required
netAmount.amountnumber---required
netAmount.currencystring-"AED", "AUD", "BDT", "BGN", "BHD", "BND", "BRL", "CAD", "CHF", "CNY", "CYP", "CZK", "DKK", "EEK", "EUR", "FJD", "GBP", "HKD", "HRK", "HUF", "IDR", "ILS", "INR", "IRR", "ISK", "JOD", "JPY", "KRW", "KWD", "LTL", "LVL", "MAD", "MMK", "MTL", "MXN", "MYR", "NAD", "NOK", "NZD", "OMR", "PGK", "PHP", "PKR", "PLN", "QAR", "ROL", "RON", "RUB", "SAR", "SEK", "SGD", "SIT", "SKK", "THB", "TRL", "TRY", "TWD", "USD", "VND", "ZAR", "ZA"-required
grossAmountobject---required
grossAmount.amountnumber---required
grossAmount.currencystring-"AED", "AUD", "BDT", "BGN", "BHD", "BND", "BRL", "CAD", "CHF", "CNY", "CYP", "CZK", "DKK", "EEK", "EUR", "FJD", "GBP", "HKD", "HRK", "HUF", "IDR", "ILS", "INR", "IRR", "ISK", "JOD", "JPY", "KRW", "KWD", "LTL", "LVL", "MAD", "MMK", "MTL", "MXN", "MYR", "NAD", "NOK", "NZD", "OMR", "PGK", "PHP", "PKR", "PLN", "QAR", "ROL", "RON", "RUB", "SAR", "SEK", "SGD", "SIT", "SKK", "THB", "TRL", "TRY", "TWD", "USD", "VND", "ZAR", "ZA"-required
vatAmountobject---required
vatAmount.amountnumber---required
vatAmount.currencystring-"AED", "AUD", "BDT", "BGN", "BHD", "BND", "BRL", "CAD", "CHF", "CNY", "CYP", "CZK", "DKK", "EEK", "EUR", "FJD", "GBP", "HKD", "HRK", "HUF", "IDR", "ILS", "INR", "IRR", "ISK", "JOD", "JPY", "KRW", "KWD", "LTL", "LVL", "MAD", "MMK", "MTL", "MXN", "MYR", "NAD", "NOK", "NZD", "OMR", "PGK", "PHP", "PKR", "PLN", "QAR", "ROL", "RON", "RUB", "SAR", "SEK", "SGD", "SIT", "SKK", "THB", "TRL", "TRY", "TWD", "USD", "VND", "ZAR", "ZA"-required
linkstringDownload link for the document--required
linkValidUntilstringTime that this download link is valid for downloading. Requesting again will generate another download link.-date-timerequired
dueDatestringOnly defined if invoice is not paid-date-time-
dueAmountallOfOnly defined if invoice is not paid---
dueAmount.0 (allOf item)object----
dueAmount.0.amountnumber---required
dueAmount.0.currencystring-"AED", "AUD", "BDT", "BGN", "BHD", "BND", "BRL", "CAD", "CHF", "CNY", "CYP", "CZK", "DKK", "EEK", "EUR", "FJD", "GBP", "HKD", "HRK", "HUF", "IDR", "ILS", "INR", "IRR", "ISK", "JOD", "JPY", "KRW", "KWD", "LTL", "LVL", "MAD", "MMK", "MTL", "MXN", "MYR", "NAD", "NOK", "NZD", "OMR", "PGK", "PHP", "PKR", "PLN", "QAR", "ROL", "RON", "RUB", "SAR", "SEK", "SGD", "SIT", "SKK", "THB", "TRL", "TRY", "TWD", "USD", "VND", "ZAR", "ZA"-required
{
  "type": "shipmentInvoiceCreated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "invoiceNumber": "string",
  "invoiceType": "creditNote",
  "status": "Cancelled",
  "lineItems": [
    {
      "description": "string",
      "costCode": "string",
      "quantity": 0,
      "vatClass": "string",
      "exchangeRate": 0,
      "unitPrice": {
        "amount": 0,
        "currency": "AED"
      },
      "netAmount": {
        "amount": 0,
        "currency": "AED"
      }
    }
  ],
  "issueDate": "2019-08-24T14:15:22Z",
  "netAmount": {
    "amount": 0,
    "currency": "AED"
  },
  "grossAmount": {
    "amount": 0,
    "currency": "AED"
  },
  "vatAmount": {
    "amount": 0,
    "currency": "AED"
  },
  "link": "string",
  "linkValidUntil": "2019-08-24T14:15:22Z",
  "dueDate": "2019-08-24T14:15:22Z",
  "dueAmount": {
    "amount": 0,
    "currency": "AED"
  }
}

Scheduled Report Generated Event

NameTypeDescriptionValueConstraintsNotes
typestringScheduled Report Generated Event Type"scheduledReportGenerated"-required
timestampstringTimestamp of the event-date-timerequired
shipmentIdstringID of the related shipment---
reportsarrayThe reports that are successfully generated.--required
reports.idstringID of the report--required
reports.documentIdstringID of the document--required
reports.documentNamestringName of the report generated--required
reports.documentCreatedAtstringName of the report generated--required
reports.formatstringFormat of the report"csv", "xlsx", "pdf"-required
reports.reportTemplateIdstringID of the report template--required
{
  "type": "scheduledReportGenerated",
  "timestamp": "2019-08-24T14:15:22Z",
  "shipmentId": "S111222",
  "reports": [
    {
      "id": "6502a1d58cac3c77dc97b112",
      "documentId": "6502a1d58cac3c77dc97b112.csv",
      "documentName": "scheduled_report_1.csv",
      "documentCreatedAt": "string",
      "format": "csv",
      "reportTemplateId": "R37065"
    }
  ]
}