Tracking Orders using the API

Using the API to Track Orders

This guide will show you how to utilize the API to be able to check tracking statuses of orders, check IMB statuses of orders (US only), create certified/registered mailings, and create webhooks to listen handle status updates. To see how to track orders via the Dashboard, check it out here.

Tracking Status

The process for checking the status of letters, postcards, and cheques are identical so this process can be generalized and applied to any collateral.

Retrieving all Orders

To check a status of an order we first need to have an order to check. If you already have an order ID you'd like to check you can skip this step. If you do not have an order ID in mind, we will need to make a GET request to either the /letters, /postcards, or /cheques endpoint. This will give us a list of orders and we can select an order from this. To narrow down our search for a specific order we can supply a search query or use pagination to refine the returned orders.

We will demonstrate this using a function that can handle fetching a list of either letters, postcards, or cheques in TypeScript.

Sample Request

enum OrderType {
  LETTERS = 'letters',
  POSTCARDS = 'postcards',
  CHEQUES = 'cheques',
  SELFMAILERS = 'selfmailers',
}

interface IGetOrderList {
 apiKey: string;
 orderType: OrderType;
 searchText?: string;
 limit?: number;
 skip?: number;
}

async function getOrderList({
  apiKey,
  orderType,
  searchText,
  limit,
  skip
}: IGetOrderInfo) {
   const requestHeaders = {
     'x-api-key': apiKey,
   }
   
   const urlSearchParams = new URLSearchParams();
  
   if (searchText) {
     urlSearchParams.append('search', searchText);
   }
  
   if (limit) {
       urlSearchParams.append('limit', limit);
   }
  
   if (skip) {
     urlSearchParams.append('skip', skip);
   }
   
   const res = await fetch(
     `https://api.postgrid.com/print-mail/v1/${orderType}${urlSearchParams.toString()}`,
     {
       method: 'GET',
       headers: requestHeaders
     }
   );
   
   return await res.json();
}

Sample Response

{
  "object": "list",
  "limit": 5,
  "skip": 10,
  "totalCount": 100,
  "data:" [
    {
      "id": "postcard_asdb8123hasd1l1As",
      "object": "postcard",
      "live": false,
      "backTemplate": "template_abc812839012381391",
      "description": "This is a test order for verifying statuses",
      "from": "contact_asd89123asdad9981",
      "frontTemplate": "template_asda1h23981njh123",
      "pageCount": 2,
      "sendDate": "2022-05-11T16:03:20.974Z",
      "size": "9x6",
      "status": "ready",
      "to": "contact_asdhksdldak91823lad",
      "url": "https://postgridpdf.url/adlakj1j298jljasdla",
      "createdAt": "2020-11-18T05:10:52.994Z",
      "updatedAt": "2020-11-18T05:10:52.994Z"
    }
  ],

}

At this point we have all the information related to the order. We can see that the order we want is the postcard with the ID postcard_asdb8123hasd1l1As. This postcard has a status of ready meaning it will be printed on the next business day.

If an order has a tracking number available, it will be in the trackingNumber field and can be used to track your order with USPS tracking.

Retrieving a Specific Order

If you have a specific order in mind we can grab its ID and use it to call either the /letters/:id, /postcards/:id, or /cheques/:id GET endpoint to retrieve all of its information.

Sample Request

enum OrderType {
  LETTERS = 'letters',
  POSTCARDS = 'postcards',
  CHEQUES = 'cheques',
  SELFMAILERS = 'selfmailers',
}

interface IGetOrderList {
 apiKey: string;
 orderType: OrderType;
 orderID: string;
}

async function getOrderList({
  apiKey,
  orderType,
  orderID,
}: IGetOrderInfo) {
   const requestHeaders = {
     'x-api-key': apiKey,
   }
   
   const res = await fetch(
     `https://api.postgrid.com/print-mail/v1/${orderType}/${orderID}`,
     {
       method: 'GET',
       headers: requestHeaders
     }
   );
   
   return await res.json();
}

Sample Response

{
  "id": "postcard_asdb8123hasd1l1As",
  "object": "postcard",
  "live": false,
  "backTemplate": "template_abc812839012381391",
  "description": "This is a test order for verifying statuses",
  "from": "contact_asd89123asdad9981",
  "frontTemplate": "template_asda1h23981njh123",
  "pageCount": 2,
  "sendDate": "2022-05-11T16:03:20.974Z",
  "size": "9x6",
  "status": "ready",
  "to": "contact_asdhksdldak91823lad",
  "url": "https://postgridpdf.url/adlakj1j298jljasdla",
  "createdAt": "2020-11-18T05:10:52.994Z",
  "updatedAt": "2020-11-18T05:10:52.994Z"
}

We can see that the status of this postcard is ready, meaning it will be printed on the next business day.

If an order has a tracking number available, it will be in the trackingNumber field and can be used to track your order with USPS tracking.

Intelligent Mail Tracking

Retrieving All Orders

To check the IMB status of an order we first need to have an order to check. If you already have an order ID you'd like to check you can skip this step. If you do not have an order ID in mind, we will need to make a GET request to either the /letters, /postcards, or /cheques endpoint. This will give us a list of orders and we can select an order from this. To narrow down our search for a specific order we can supply a search query or use pagination to refine the returned orders.

Sample Request

enum OrderType {
  LETTERS = 'letters',
  POSTCARDS = 'postcards',
  CHEQUES = 'cheques',
  SELFMAILERS = 'selfmailers',
}

interface IGetOrderList {
 apiKey: string;
 orderType: OrderType;
 searchText?: string;
 limit?: number;
 skip?: number;
}

async function getOrderList({
  apiKey,
  orderType,
  searchText,
  limit,
  skip
}: IGetOrderInfo) {
   const requestHeaders = {
     'x-api-key': apiKey,
   }
   
   const urlSearchParams = new URLSearchParams();
  
   if (searchText) {
     urlSearchParams.append('search', searchText);
   }
  
   if (limit) {
       urlSearchParams.append('limit', limit);
   }
  
   if (skip) {
     urlSearchParams.append('skip', skip);
   }
   
   const res = await fetch(
     `https://api.postgrid.com/print-mail/v1/${orderType}${urlSearchParams.toString()}`,
     {
       method: 'GET',
       headers: requestHeaders
     }
   );
   
   return await res.json();
}

Sample Response

{
  "object": "list",
  "limit": 5,
  "skip": 10,
  "totalCount": 100,
  "data:" [
    {
      "id": "postcard_asdb8123hasd1l1As",
      "object": "postcard",
      "live": false,
      "backTemplate": "template_abc812839012381391",
      "description": "This is a test order for verifying statuses",
      "from": "contact_asd89123asdad9981",
      "frontTemplate": "template_asda1h23981njh123",
      "imbStatus": "entered_mail_stream",
      "pageCount": 2,
      "sendDate": "2022-05-11T16:03:20.974Z",
      "size": "9x6",
      "status": "processed_for_delivery",
      "to": "contact_asdhksdldak91823lad",
      "url": "https://postgridpdf.url/adlakj1j298jljasdla",
      "createdAt": "2020-11-18T05:10:52.994Z",
      "updatedAt": "2020-11-18T05:10:52.994Z"
    }
  ],

}

In this case, the postcard we are interested in has the ID postcard_asdb8123hasd1l1As. We can see that it has an IMB status of entered_mail_stream meaning it has entered a USPS facility. For a full list of possible values, see here.

If an order has a tracking number available, it will be in the trackingNumber field and can be used to track your order with USPS tracking.

Retrieving a Specific Order

If you have a specific order in mind we can grab its ID and use it to call either the /letters/:id, /postcards/:id, or /cheques/:id GET endpoint to retrieve all of its information.

Sample Request

enum OrderType {
  LETTERS = 'letters',
  POSTCARDS = 'postcards',
  CHEQUES = 'cheques',
  SELFMAILERS = 'selfmailers',
}

interface IGetOrderList {
 apiKey: string;
 orderType: OrderType;
 orderID: string;
}

async function getOrderList({
  apiKey,
  orderType,
  orderID,
}: IGetOrderInfo) {
   const requestHeaders = {
     'x-api-key': apiKey,
   }
   
   const res = await fetch(
     `https://api.postgrid.com/print-mail/v1/${orderType}/${orderID}`,
     {
       method: 'GET',
       headers: requestHeaders
     }
   );
   
   return await res.json();
}

Sample Response

{
  "id": "postcard_asdb8123hasd1l1As",
  "object": "postcard",
  "live": false,
  "backTemplate": "template_abc812839012381391",
  "description": "This is a test order for verifying statuses",
  "from": "contact_asd89123asdad9981",
  "frontTemplate": "template_asda1h23981njh123",
  "imbStatus": "entered_mail_stream",
  "pageCount": 2,
  "sendDate": "2022-05-11T16:03:20.974Z",
  "size": "9x6",
  "status": "entered_mail_stream",
  "to": "contact_asdhksdldak91823lad",
  "url": "https://postgridpdf.url/adlakj1j298jljasdla",
  "createdAt": "2020-11-18T05:10:52.994Z",
  "updatedAt": "2020-11-18T05:10:52.994Z"
}

We can see that this postcard has an IMB status of entered_mail_stream meaning it has entered a USPS facility. For a full list of possible values, see here.

If an order has a tracking number available, it will be in the trackingNumber field and can be used to track your order with USPS tracking.

Certified and Registered Mail

To create a certified/registered mail order we will need to make a POST request to either the /letters or /cheques endpoint as this is only available for letters and cheques. For this example we will be creating a letter.

See the following for a more in-depth guide on creating orders: letters, cheques.

Creating a Certified/Registered Mail Order

Sample Request

enum ExtraService {
  CERTIFIED_MAIL = 'certified',
  CERTIFIED_RETURN_RECEIPT = 'certified_return_receipt',
  REGISTERED = 'registered',
}

interface ICreateCheque {
  apiKey: string;
  toContactID: string;
  fromContactID: string;
  bankAccountID: string;
  // in cents
  amount: number;
  extraService?: ExtraService;
}

async function createCheque({
  apiKey,
  toContactID,
  fromContactID,
  bankAccountID,
  amount,
  extraService
}: ICreateCheque) {
  
  const requestHeaders = {
    'x-api-key': apiKey,
    'Content-Type': 'application/json',
  }
  
  const requestBody = {
    to: toContactID,
    from: fromContactID,
    bankAccount: bankAccountID,
    amount: amount,
    extraService: extraService,
  }
  
  const res = await fetch('https://api.postgrid.com/print-mail/v1/cheques', {
    method: 'POST',
    headers: requestHeaders,
    body: JSON.stringify(requestBody)
  });
  
  return await res.json();
}

Sample Response

{
  "id": "cheque_mK8PfefU41ZQnKBRCaUayh",
  "object": "cheque",
  "live": false,
  "amount": 10000,
  "bankAccount": {
    "id": "bank_3yXGjQ6ee4pHkLhb3a7keu",
    "object": "bank_account",
    "live": false,
    "accountNumberLast4": "3211",
    "bankCountryCode": "CA",
    "bankName": "CIBC",
    "bankPrimaryLine": "100 Bank Street",
    "bankSecondaryLine": "Toronto, ON M9V4V1",
    "description": "Example Bank Account",
    "routeNumber": "123",
    "transitNumber": "12345",
    "createdAt": "2020-11-13T08:28:42.509Z",
    "updatedAt": "2020-11-13T08:28:42.509Z"
  },
  "description": "Certified Mail",
  "extraService": "certified",
  "from": {
    "id": "contact_jkTtb6ovKZ7xXe7N5rXipX",
    "object": "contact",
    "addressLine1": "20-20 BAY ST",
    "addressLine2": "FLOOR 11",
    "addressStatus": "verified",
    "city": "TORONTO",
    "companyName": "PostGrid",
    "country": null,
    "countryCode": null,
    "postalOrZip": "M5V 4G9",
    "provinceOrState": "ON"
  },
  "memo": "A short memo.",
  "number": 5049,
  "sendDate": "2020-11-18",
  "status": "ready",
  "to": {
    "id": "contact_jkTtb6ovKZ7xXe7N5rXipX",
    "object": "contact",
    "addressLine1": "20-20 BAY ST",
    "addressLine2": "FLOOR 11",
    "addressStatus": "verified",
    "city": "TORONTO",
    "companyName": "PostGrid",
    "country": null,
    "countryCode": null,
    "postalOrZip": "M5V 4G9",
    "provinceOrState": "ON"
  },
  "createdAt": "2020-11-18T05:10:52.994Z",
  "updatedAt": "2020-11-18T05:10:52.994Z"
}

We can see that the cheque has the extraService field with a value of certified meaning that this order will be sent as certified mail.

If an order has a tracking number available, it will be in the trackingNumber field and can be used to track your order with USPS tracking.

Using Webhooks to Handle Status Updates

Webhooks can be used to notify your application when events occur in PostGrid. These webhooks for example, can be used to send you a notification when an order has been processed for delivery.

Creating a Webhook

To create a webhook you will need to make a POST request to the /webhooks endpoint. At minimum, you will need to provide a URL for the webhook as well as the enabled events. For an exhaustive list of what you can pass, see our webhook documentation here.

Sample Request

enum WebhookEvent {
  LETTER_CREATED = 'letter.created',
  LETTER_UPDATED = 'letter.updated',
  POSTCARD_CREATED = 'postcard.created',
  POSTCARD_UPDATED = 'postcard.updated',
  CHEQUE_CREATED = 'cheque.created',
  CHEQUE_UPDATED = 'cheque.updated',
  SELF_MAILER_CREATED = 'self_mailer.created',
  SELF_MAILER_UPDATED = 'self_mailer.updated',
  RETURN_ENVELOPE_ORDER_CREATED = 'return_envelope_order.created',
  RETURN_ENVELOPE_ORDER_UPDATED = 'return_envelope_order.updated',
}

interface ICreateWebhook {
  apiKey: string;
  url: string;
  enabledEvents: WebhookEvent[];
}

async function createWebhook({
  apiKey,
  url,
  enabledEvents,
}: ICreateWebhook) {
  
  const requestHeaders = {
    'x-api-key': apiKey,
    'Content-Type': 'application/json',
  }
  
  const requestBody = {
    url,
    enabledEvents
  }
  
  const res = await fetch('https://api.postgrid.com/print-mail/v1/webhooks', {
    method: 'POST',
    headers: requestHeaders,
    body: JSON.stringify(requestBody),
  }
                          
  return await res.json();
}

Sample Response

{
  "id": "webhook_5z5e7KLCKiXn84okdzCiBg",
  "object": "webhook",
  "live": false,
  "enabled": true,
  "enabledEvents": [
      "letter.created"
  ],
  "payloadFormat": "jwt",
  "secret": "webhook_secret_a9ryNLNqv5FA2vTKTxJby7",
  "url": "https://myfancyurlformypostgridwebhook.com/listen-for-events",
  "createdAt": "2022-11-01T17:54:54.869Z",
  "updatedAt": "2022-11-01T17:54:54.869Z"
}

We now have a newly created webhook. Webhook payloads can be delivered in two formats, JSON Web Token (JWT) and JSON. The payload format of the webhook can be selected during creation, or updated accordingly afterwards. Note that updating a webhook's payload format will only affect future webhook invocations. By default, created webhooks will use the JWT payload format. Both formats require a webhook secret, which is required to either decode or validate the payload respectively.

When you receive an event with a JWT payload, you can verify it using a JWT library available for your particular language (using the HMAC SHA256 Algorithm). There are many off-the-shelf solutions you can use.

For JSON payloads, the data associated with the event is delivered in JSON, but the origin of the data must be verified. To verify that this webhook invocation originated from PostGrid, the webhook event is signed using the webhook secret, event payload, as well as a timestamp, and the signature is sent in the request headers of the invocation.

You must respond with a 200 status from your webhook. Otherwise, PostGrid will retry the webhook up to 3 times. For more information, check out our webhooks guide.