Skip to content

Webhook and Email Notifications

jimbali edited this page Apr 5, 2024 · 5 revisions

Webhook Notifications

1. Introduction

Webhook notifications allow suppliers to subscribe to the Book a Secure Move API system and receive notifications of when a move record is created or updated. The notification is transmitted via an HTTP POST to an endpoint specified by the supplier. The notification is signed with a shared key to ensure the authenticity of the message. If the endpoint is unavailable then the Book a Secure Move system will re-attempt the delivery later, retrying up to 25 times.

2. Subscribing to notifications

The subscription process is currently manual - please contact the Book a Secure Move team via your Slack channel with the following details:

  • Callback URL: this must be an https:// endpoint which is publicly accessible and should not require authentication
  • Secret: this is a shared secret which is used to generate a SHA-256 HMAC signature to guarantee the authenticity of the notification

If your server supports HTTP Basic Authorization, you can also supply:

  • Username: optional, used for basic authorization
  • Password: optional, used for basic authorization

Once the Book a Secure Move team have actioned the request, notifications of moves events will be immediately sent to the specified callback_url.

There is also the ability to send emails as well as webook notifications. Each supplier has two entries in the subscriptions table, one for webhooks and one for email notifications, you can enable and disable these by changing the enabled flag to true or false depending on the suppliers needs.

3. Receiving notifications

The supplier should ensure that the designated endpoint is available to receive notifications at all times. If for any reason the endpoint is offline when a notification is attempted, the system will retry the notification later with an increasing random exponential delay. The system will continue to retry the notification up to 25 times. On the first day the notification will be attempted approximately 14 times (see this table for the approximate schedule). Once 25 failed delivery attempts are reached the notification record will be failed and will not be attempted again.

When a notification is received at the supplier's endpoint, it must:

  1. Verify the PECS-SIGNATURE header of the notification
  2. Upon verification return an HTTP success code (in the range 200-299)

The notification is not considered to have been delivered until an HTTP success is received.

The endpoint should return success (or failure) immediately after signature verification and success should not be contingent on subsequent processing.

I.e. if a notification was successfully received and the signature verified, it is not correct to return a failure code because of some other problem in the subsequent processing as this would compromise the decoupled nature of the systems.

An example move notification message is given below:

POST / HTTP/1.1
Host: webhook-receiver.example.com
Accept: */*
Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Connection: keep-alive
Content-Length: 415
Content-Type: application/vnd.api+json
Keep-Alive: 30
Pecs-Notification-Id: 2cb108dd-8d47-4a5f-8d36-29324a770f05
Pecs-Signature: 5cWQEe9emC7Myvj8jxVDIWI0jxoshOhitXfsCQBtTS4=
User-Agent: pecs-webhooks/v1
{
  "data": {
    "id": "2cb108dd-8d47-4a5f-8d36-29324a770f05",
    "type": "notifications",
    "attributes": {
      "event_type": "create_move",
      "timestamp": "2020-02-18T11:05:00+00:00"
    },
    "relationships": {
      "move": {
        "data": {
          "id": "149f1c27-1b7d-4c60-a4d4-ae8afbe92501",
          "type": "moves"
        },
        "links": {
          "self": "https://server/api/moves/149f1c27-1b7d-4c60-a4d4-ae8afbe92501"
        }
      }
    }
  }
}

4. Verification of the PECS-SIGNATURE header

It is necessary to verify the signature of the notification with the following algorithm in order to guarantee the authenticity of the message:

  1. Calculate the SHA-256 HMAC of the message body using the pre-agreed <SECRET> when the subscription was created
  2. Base64 encode the calculated HMAC
  3. Check that the encoded value matches the PECS-SIGNATURE header. If it does match, return an HTTP success status (e.g. 202 - Accepted). If it does not match, return an error code (e.g. 403 - Forbidden).

Ruby code for calculating the signature is given below:

require 'base64'
require 'openssl'
expected_signature = Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', secret, body))

5. Idempotent nature of notifications

The supplier should store the id (also in the PECS-NOTIFICATION-ID header) of every successful notification received. If a subsequent notification with the same id is received, it should be ignored.

In the event that the notification is ultimately ignored it is still necessary to return a success status (e.g. 202 - Accepted).

6. Random order of notifications

The supplier should be mindful that because notifications may need to be retried several times before they are successfully delivered, the order in which they are received will not necessarily match the order in which the events occurred. For example, it is possible for the update_move notification to be received before the create_move notification, if the create_move notification was not successfully delivered on the first try.

To allow for this, the supplier must always fetch the latest move record from the API and must not rely on the event_type field in the notification.

7. Processing notifications

The notification typically contains minimal data: an id, a timestamp, an event_type, a move:id and a link to the move record:

{
  "data": {
    "id": "0706f16b-d849-4f3e-a324-6a43bca5f0e5",
    "type": "notifications",
    "attributes": {
      "event_type": "update_move",
      "timestamp": "2020-02-18T17:43:08+00:00"
    },
    "relationships": {
      "move": {
        "data": {
          "id": "149f1c27-1b7d-4c60-a4d4-ae8afbe92501",
          "type": "moves"
        },
        "links": {
          "self": "https://server/api/moves/149f1c27-1b7d-4c60-a4d4-ae8afbe92501"
        }
      }
    }
  }
}

Upon receiving (any) notification the supplier should always retrieve the latest record using the self link in the JSON document.

Please note that the update_move event does not imply that a move record requires updating on the Book a Secure Move API: rather, the supplier should retrieve the latest record from the API and then take any further action as neccesary (if any).

For example, updating a move status from requested => booked on the API will in turn trigger an update_move_status notification. In that case the supplier should retrieve the latest record but no further action is required.

8. Types of notification

Create Move

When a move is first created within the system, a notification of event_type: create_move is sent. This will normally be followed by an Update Move, and as it progresses will also have Update Move Status events.

Update Move

Move updates come through as a notification of event_type: update_move. Suppliers should expect to receive these when either the move itself is updated or the associated Profile/Person is updated. The only exception is where the move's status is updated in the same transaction. In that case, an Update Move Status notification will be sent and the supplier should also check whether there are associated move changes.

Update Move Status

If the status of a move is changed, a notification of event_type: update_move_status will be sent. This will mean that either just the status of the move has changed, or some other details have changed and the status has changed. A supplier is expected to watch for a move status of requested in order to process the move booking. This is considered formal notification of the move.

Complete Youth Risk Assessment

When a Youth Risk Assessment is first completed a notification of event_type: complete_youth_risk_assessment will be sent. Note that the relationship and associated link within this notification is the actual assessment, rather than the move associated with the assessment. A supplier is expected to check the Youth Risk Assessment linked in the notification to ensure all data and records are up to date.

Example:

{
  "data": {
    "id": "ffffd468-6020-42f9-8fc8-84164f169ceb",
    "type": "notifications",
    "attributes": {
      "event_type": "complete_youth_risk_assessment",
      "timestamp": "2020-06-18T12:32:37+01:00"
    },
    "relationships": {
      "youth_risk_assessment": {
        "data": {
          "id": "fe49dd5e-d783-476e-9e67-51cdb630e948",
          "type": "youth_risk_assessments"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/youth_risk_assessments/fe49dd5e-d783-476e-9e67-51cdb630e948"
        }
      }
    }
  }
}

Confirm Youth Risk Assessment

If the status of a Youth Risk Assessment is changed to "confirmed", a notification of event_type: confirm_youth_risk_assessment will be sent. Note that the relationship and associated link within this notification is the actual assessment, rather than the move associated with the assessment. A supplier is expected to check the Youth Risk Assessment linked in the notification to ensure all data and records are up to date.

Example:

{
  "data": {
    "id": "ffffd468-6020-42f9-8fc8-84164f169ceb",
    "type": "notifications",
    "attributes": {
      "event_type": "confirm_youth_risk_assessment",
      "timestamp": "2020-06-18T12:32:37+01:00"
    },
    "relationships": {
      "youth_risk_assessment": {
        "data": {
          "id": "fe49dd5e-d783-476e-9e67-51cdb630e948",
          "type": "youth_risk_assessments"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/youth_risk_assessments/fe49dd5e-d783-476e-9e67-51cdb630e948"
        }
      }
    }
  }
}

Complete Person Escort Record

When a Person Escort Record is first completed a notification of event_type: complete_person_escort_record will be sent. Note that the relationship and associated link within this notification is the actual PER, rather than the move associated with the PER. A supplier is expected to check the Person Escort Record linked in the notification to ensure all data and records are up to date.

Example:

{
  "data": {
    "id": "ffffd468-6020-42f9-8fc8-84164f169ceb",
    "type": "notifications",
    "attributes": {
      "event_type": "complete_person_escort_record",
      "timestamp": "2020-06-18T12:32:37+01:00"
    },
    "relationships": {
      "person_escort_record": {
        "data": {
          "id": "fe49dd5e-d783-476e-9e67-51cdb630e948",
          "type": "person_escort_records"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/person_escort_records/fe49dd5e-d783-476e-9e67-51cdb630e948"
        }
      }
    }
  }
}

Amend Person Escort Record

If a Person Escort Record is amended after completion a notification of event_type: amend_person_escort_record will be sent. Note that the relationship and associated link within this notification is the actual PER, rather than the move associated with the PER. A supplier is expected to check the Person Escort Record linked in the notification to ensure all data and records are up to date.

Example:

{
  "data": {
    "id": "ffffd468-6020-42f9-8fc8-84164f169ceb",
    "type": "notifications",
    "attributes": {
      "event_type": "amend_person_escort_record",
      "timestamp": "2020-06-18T12:32:37+01:00"
    },
    "relationships": {
      "person_escort_record": {
        "data": {
          "id": "fe49dd5e-d783-476e-9e67-51cdb630e948",
          "type": "person_escort_records"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/person_escort_records/fe49dd5e-d783-476e-9e67-51cdb630e948"
        }
      }
    }
  }
}

Handover Person Escort Record

Once a Person Escort Record is handed over a notification of event_type: handover_person_escort_record will be sent. Note that the relationship and associated link within this notification is the actual PER, rather than the move associated with the PER. A supplier is expected to check the Person Escort Record linked in the notification to ensure all data and records are up to date, as handover details can be amended in the Person Escort Record.

Example:

{
  "data": {
    "id": "ffffd468-6020-42f9-8fc8-84164f169ceb",
    "type": "notifications",
    "attributes": {
      "event_type": "handover_person_escort_record",
      "timestamp": "2020-06-18T12:32:37+01:00"
    },
    "relationships": {
      "person_escort_record": {
        "data": {
          "id": "fe49dd5e-d783-476e-9e67-51cdb630e948",
          "type": "person_escort_records"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/person_escort_records/fe49dd5e-d783-476e-9e67-51cdb630e948"
        }
      }
    }
  }
}

Create Event

If an event is created on a move or a person escort record by a non-supplier user (for example, police users while someone is held in police custody), this will be sent to suppliers using the create_event event type. Relationships to the associated move, person escort record and event will be included.

Example:

{
  "data": {
    "id": "ffffd468-6020-42f9-8fc8-84164f169ceb",
    "type": "notifications",
    "attributes": {
      "event_type": "create_event",
      "timestamp": "2020-06-18T12:32:37+01:00"
    },
    "relationships": {
      "event": {
        "data": {
          "id": "946405b0-c97a-466e-9294-5e1490f2a5dd",
          "type": "events"
        }
      },
      "person_escort_record": {
        "data": {
          "id": "fe49dd5e-d783-476e-9e67-51cdb630e948",
          "type": "person_escort_records"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/person_escort_records/fe49dd5e-d783-476e-9e67-51cdb630e948"
        }
      },
      "move": {
        "data": {
          "id": "6a4a1e56-9f9b-403b-a12e-76715492fcb3",
          "type": "moves"
        },
        "links": {
          "self": "http://localhost:3000/api/v1/moves/6a4a1e56-9f9b-403b-a12e-76715492fcb3"
        }
      }
    }
  }
}

Create Lodging

When a lodging is scheduled, suppliers will be notified via a create_lodging notification.

Example:

{
  "data": {
    "id": "8155f35b-5035-479b-8e7b-e9ff5df99f73",
    "type": "notifications",
    "attributes": {
      "event_type": "create_lodging",
      "timestamp": "2024-04-05 14:52:35 +0100"
    },
    "relationships": {
      "move": {
        "data": {
          "id": "d6e231b6-45de-466c-81de-3ce503b71c70",
          "type": "moves"
        },
        "links": {
          "self": "http://hmpps-book-secure-move-api-staging.apps.cloud-platform.service.justice.gov.uk/api/v1/moves/d6e231b6-45de-466c-81de-3ce503b71c70"
        }
      },
      "lodging": {
        "data": {
          "id": "ae8e045b-9a99-4218-b283-001379d33fde",
          "type": "lodgings"
        },
        "links": {
          "self": "http://hmpps-book-secure-move-api-staging.apps.cloud-platform.service.justice.gov.uk/api/v1/moves/d6e231b6-45de-466c-81de-3ce503b71c70/lodgings/ae8e045b-9a99-4218-b283-001379d33fde"
        }
      }
    }
  }
}

Update Lodging

When the details of a lodging are updated, suppliers will be notified via an update_lodging notification.

Example:

{
  "data": {
    "id": "c6315785-b827-4a61-81b9-fbd8cda6fd19",
    "type": "notifications",
    "attributes": {
      "event_type": "update_lodging",
      "timestamp": "2024-03-28 15:57:04 +0000"
    },
    "relationships": {
      "move": {
        "data": {
          "id": "b88eb60e-3204-4887-8d91-92de55c551cb",
          "type": "moves"
        },
        "links": {
          "self": "http://hmpps-book-secure-move-api-staging.apps.cloud-platform.service.justice.gov.uk/api/v1/moves/b88eb60e-3204-4887-8d91-92de55c551cb"
        }
      },
      "lodging": {
        "data": {
          "id": "e3f019d9-8e4e-43b5-9b87-fd3c32a7f082",
          "type": "lodgings"
        },
        "links": {
          "self": "http://hmpps-book-secure-move-api-staging.apps.cloud-platform.service.justice.gov.uk/api/v1/moves/b88eb60e-3204-4887-8d91-92de55c551cb/lodgings/e3f019d9-8e4e-43b5-9b87-fd3c32a7f082"
        }
      }
    }
  }
}

Cancel Lodging

When a lodging is cancelled, suppliers will be notified via a cancel_lodging notification.

Example:

{
  "data": {
    "id": "ef0d7f9e-aa5c-4b15-8172-4f76333a4cc0",
    "type": "notifications",
    "attributes": {
      "event_type": "cancel_lodging",
      "timestamp": "2024-03-28 16:09:20 +0000"
    },
    "relationships": {
      "move": {
        "data": {
          "id": "5498d74d-bb96-42a4-9360-4e970676e60d",
          "type": "moves"
        },
        "links": {
          "self": "http://hmpps-book-secure-move-api-staging.apps.cloud-platform.service.justice.gov.uk/api/v1/moves/5498d74d-bb96-42a4-9360-4e970676e60d"
        }
      },
      "lodging": {
        "data": {
          "id": "43acabb6-100c-4e1e-ae1a-8085656852ae",
          "type": "lodgings"
        },
        "links": {
          "self": "http://hmpps-book-secure-move-api-staging.apps.cloud-platform.service.justice.gov.uk/api/v1/moves/5498d74d-bb96-42a4-9360-4e970676e60d/lodgings/43acabb6-100c-4e1e-ae1a-8085656852ae"
        }
      }
    }
  }
}
Clone this wiki locally