From fb03b1154350a67ca668bf4e3b6e08bc94f522cc Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 24 Sep 2026 17:30:38 +0100 Subject: [PATCH 1/5] added 'make serve-watch' --- Makefile | 8 +++ README.md | 1 + package-lock.json | 162 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 4 files changed, 174 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 02c66a0b8..ebf9efbb9 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,14 @@ serve: (sleep 5; python3 -m webbrowser http://127.0.0.1:5000) & npm run serve +#Serve the OAS specification with live reload on source edits +serve-watch: + npm run publish + npm run watch & WATCH_PID=$$!; \ + trap "kill $$WATCH_PID 2>/dev/null" EXIT INT TERM; \ + (sleep 5; python3 -m webbrowser http://127.0.0.1:5000) & \ + npm run serve + #Check dependencies for licensing issues .check-licenses: npm run check-licenses diff --git a/README.md b/README.md index aa77b1f8e..6c85dad41 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ There are `make` commands that alias some of this functionality: * `lint` -- Lints the spec and code * `publish` -- Outputs the specification as a **single file** into the `build/` directory * `serve` -- Serves a preview of the specification in human-readable format - your browser will automatically open the documentation +* `serve-watch` -- Serves a preview of the specification in human-readable format that is updated as you make changes * `build-test-documentation` -- Builds the test documentation that is checked into the repository under `docs/tests` ## Testing diff --git a/package-lock.json b/package-lock.json index 9732fb539..edae694ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "license-checker": "^25.0.1", "minimist": "^1.2.2", "newman": "^6.2.2", + "nodemon": "^3.1.0", "sinon": "^17.0.1", "typescript": "^5.5.4" } @@ -4466,6 +4467,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -6182,6 +6190,110 @@ "node": ">=18" } }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -7278,6 +7390,13 @@ "url": "https://github.com/sponsors/lupomontero" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8267,6 +8386,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/simple-websocket": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", @@ -8820,6 +8965,16 @@ "node": ">=8.0" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -9058,6 +9213,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/underscore": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", diff --git a/package.json b/package.json index b079de7d9..ab2f9f318 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "sourceType": "module", "scripts": { "lint": "redocly lint specification/communications-manager.yaml", - "publish": "mkdir -p build && redocly bundle specification/communications-manager.yaml --dereferenced --remove-unused-components --ext json | poetry run python scripts/set_version.py > build/communications-manager.json", + "publish": "mkdir -p build && redocly bundle specification/communications-manager.yaml --dereferenced --remove-unused-components --ext json | poetry run python scripts/set_version.py > build/communications-manager.json.tmp && mv build/communications-manager.json.tmp build/communications-manager.json", "serve": "redocly preview-docs -p 5000 build/communications-manager.json", + "watch": "nodemon --watch specification --ext yaml,md,json --ignore build --exec \"npm run publish\"", "check-licenses": "node_modules/.bin/license-checker --failOn GPL --failOn LGPL --failOn AGPL", "sandbox-postman-collection": "newman run postman/NhsNotify.Sandbox.postman_collection.json", "integration-postman-collection": "poetry run python scripts/build_postman_environment.py && newman run postman/NhsNotify.Integration.postman_collection.json -e postman/Integration.test.postman_environment.json", @@ -46,6 +47,7 @@ "license-checker": "^25.0.1", "minimist": "^1.2.2", "newman": "^6.2.2", + "nodemon": "^3.1.0", "sinon": "^17.0.1", "typescript": "^5.5.4" }, From 048b32187e22d7d9a227489c1f05eb74dc34f4a4 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 24 Sep 2026 19:18:32 +0100 Subject: [PATCH 2/5] updated docs for callbacks mTLS and added CA certs --- package.json | 2 +- scripts/inline_assets.py | 52 +++++++++++++++++++ specification/documentation/APIDescription.md | 22 +++++--- .../documentation/certs/nonprod-ca.crt | 31 +++++++++++ specification/documentation/certs/prod-ca.crt | 31 +++++++++++ 5 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 scripts/inline_assets.py create mode 100644 specification/documentation/certs/nonprod-ca.crt create mode 100644 specification/documentation/certs/prod-ca.crt diff --git a/package.json b/package.json index ab2f9f318..7ba0e29a5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "sourceType": "module", "scripts": { "lint": "redocly lint specification/communications-manager.yaml", - "publish": "mkdir -p build && redocly bundle specification/communications-manager.yaml --dereferenced --remove-unused-components --ext json | poetry run python scripts/set_version.py > build/communications-manager.json.tmp && mv build/communications-manager.json.tmp build/communications-manager.json", + "publish": "mkdir -p build && redocly bundle specification/communications-manager.yaml --dereferenced --remove-unused-components --ext json | poetry run python scripts/set_version.py | poetry run python scripts/inline_assets.py > build/communications-manager.json.tmp && mv build/communications-manager.json.tmp build/communications-manager.json", "serve": "redocly preview-docs -p 5000 build/communications-manager.json", "watch": "nodemon --watch specification --ext yaml,md,json --ignore build --exec \"npm run publish\"", "check-licenses": "node_modules/.bin/license-checker --failOn GPL --failOn LGPL --failOn AGPL", diff --git a/scripts/inline_assets.py b/scripts/inline_assets.py new file mode 100644 index 000000000..fa31c34d7 --- /dev/null +++ b/scripts/inline_assets.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +""" +inline_assets.py + +Reads an openapi spec on stdin, replaces asset tokens with base64 `data:` +URIs so documentation download links are self-contained (no external +hosting), then prints the spec on stdout. +""" +import sys +import os +import json +import base64 + +# Maps a token found in the spec (typically within info.description) to the +# repo-relative asset whose base64-encoded contents replace it at build time. +ASSET_TOKENS = { + "{{NONPROD_CA_B64}}": "specification/documentation/certs/nonprod-ca.crt", + "{{PROD_CA_B64}}": "specification/documentation/certs/prod-ca.crt", +} + + +def _repo_root(): + return os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +def embed_inline_assets(spec): + """Replace asset tokens anywhere in the spec with base64 of the asset. + + A token is only resolved if it is present in the serialised spec. If a + referenced asset file is missing, the build fails loudly. + """ + serialised = json.dumps(spec) + for token, rel_path in ASSET_TOKENS.items(): + if token not in serialised: + continue + path = os.path.join(_repo_root(), rel_path) + with open(path, "rb") as asset: + encoded = base64.b64encode(asset.read()).decode("ascii") + serialised = serialised.replace(token, encoded) + return json.loads(serialised) + + +def main(): + """Main entrypoint""" + data = json.loads(sys.stdin.read()) + data = embed_inline_assets(data) + sys.stdout.write(json.dumps(data, indent=2)) + sys.stdout.close() + + +if __name__ == "__main__": + main() diff --git a/specification/documentation/APIDescription.md b/specification/documentation/APIDescription.md index d36639bf4..73ecf9a1d 100644 --- a/specification/documentation/APIDescription.md +++ b/specification/documentation/APIDescription.md @@ -199,23 +199,32 @@ Errors specific to each API are shown in the Endpoints section, under Response. ## Receive a callback -You may develop one or many endpoints on your service if you want to receive callbacks from NHS Notify. +You may develop one or multiple endpoints on your service if you want to receive callbacks from NHS Notify. We have created an OpenAPI specification detailing the behaviour of the endpoint that consumers should create to subscribe to callbacks. ### Mutual TLS -**This feature is currently under development and is not yet ready to use.** +Our new callbacks mechanism uses Mutual TLS (mTLS) to verify the autheticity of the callback to ensure that it has come from NHS Notify. + +Your service must request the client certificate during the callback and you must verify the following: -We are currently developing a new callbacks service which uses mutual TLS (mTLS). +1. **Chain of trust**: the certificate be signed by the correct CA for the environment (see table below). +2. **Certificate expiry**: ensure that the current date and time is not outside `notBefore` and `notAfter`. -If you are using the newer callbacks mechanism, then your service must check the client certificate which is presented to your service during the callback in order to verify that the response has come from NHS Notify. Your service must reject connection attempts where the client certificate can not be verified by the NHS Notify root CA certificate. +In addition, we recommend that you also verify the following fields: -If the client certificate can not be verified, your service should respond with `403 Forbidden`. +* The DN Common Name (CN) is: `NHS Notify Callbacks` +* The Subject Alternative Name (SAN) is correct for the environment (see table below) + +| Environment | CA Certificate | SAN URI | +| --- | --- | --- | +| Integration | Download | `spiffe://callbacks.nonprod.nhsnotify.national.nhs.uk/int/client` | +| Production | Download | `spiffe://callbacks.prod.nhsnotify.national.nhs.uk/main/client` | ### HMAC-SHA256 signature checking -If you are still using the older (original) callbacks mechanism, mTLS is not supported. +If you are still using our older (original) callbacks mechanism, mTLS is not supported, and neither are the new callbacks for `RecipientResponse` and `ReturnedMail`. Instead, we will send your API key in the `x-api-key` header. Your service should respond with: @@ -223,6 +232,7 @@ Instead, we will send your API key in the `x-api-key` header. Your service shoul * `401 Unauthorized` if the API key is invalid We will send you a HMAC-SHA256 signature in the `x-hmac-sha256-signature` header. You will need to validate the signature to verify the response has come from NHS Notify. + This can be achieved by hashing the request body using the HMAC-SHA256 algorithm with a secret value that is comprised of a concatenation of your APIM application ID and the API key that we provide you. The secret takes the following form `[APPLICATION_ID].[API_KEY]`. If you receive a request with an invalid signature you should ignore it and respond with a `403 Forbidden`. ### Deduplication diff --git a/specification/documentation/certs/nonprod-ca.crt b/specification/documentation/certs/nonprod-ca.crt new file mode 100644 index 000000000..d2049b0fa --- /dev/null +++ b/specification/documentation/certs/nonprod-ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQNB/CGkV5l+oRKJB8UA9qrTANBgkqhkiG9w0BAQsFADBM +MQswCQYDVQQGEwJHQjEUMBIGA1UECgwLTkhTIEVuZ2xhbmQxJzAlBgNVBAMMHm5o +cy1tYWluLWFjY3QtY2xpZW50LWNhbGxiYWNrczAeFw0yNjA5MTAxMzU3MTdaFw0z +NjA5MTAxNDU3MTdaMEwxCzAJBgNVBAYTAkdCMRQwEgYDVQQKDAtOSFMgRW5nbGFu +ZDEnMCUGA1UEAwwebmhzLW1haW4tYWNjdC1jbGllbnQtY2FsbGJhY2tzMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAllElBxYL1F+3hRfGKF/Ahnd6ApJZ +t+Tkfzd1wgLY9MSh0FmKNIdVWN9p8yJ7mDbPHfulXabO9i7xW2pw/RsSnfML47Vl +8SbhnvQo4BOacbW91UFJaR/G+69ILD4Ez79ns8dug0EzGdCDDMZUnyQiZHJbJ6KX +Xjv15ARaXY/FUNgF1sjbiRKJVdhodG1txtxLIk/jgcl6eFVLXwsCUBOjlSw7UUpE +7Pq7xSls4tHEkVAuqP8uGEgpjLrSsHbDYR5t283SyhE4iqCP1a2mzpzF06M+2xXm +FhJH/GefUJK1zCSx4DbPGNOYE8HeoUuw3BmmVxzjj+TwOQvzrOF4amwlHFUkFiTE +bhcD014p+5O/j4g6cnGW4Dtb6hC9xHmsobNibPFm8KFTv4wFsmTkwwKwe8dNKpoI +j2l0mEdmt3Bboi0KI1DYbCmlGDMUcTk+4Wov92pMo6Cb4P4eS0PAdAg2DcxjHvcT +FJpCLVCKRUKmfpf9VCAiZv1/fEFVAc3Sy2JSAb+AAnJeXAIVhnELqEuVzswHaSot +9QZGydPfnCv2Sx2IN0Y9n7bfX0Tzum9tJ1JIg0mh3+0pcDgWAFo8/eP3SSjqINmf +B+uXKl2uQxHemok8oOkfReUz4XgB5h5FcrsocQcFgReaTLirq2XFKHGBS7qH1Pih ++CS+mQyrK185eyUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +hLjBRZz5cb8+w8zUaRrkG0o2S1UwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4ICAQBQtUky8dq0AmElK/NdGDGlVr+PRUV8OnEaHEhmiInvc1xPpbZb1NnY +If8oyEH/V3IWMNFcdJ6dAagVo1bl2wcyEEdDlHezCx7hMtjn6qLqjA9G0y8No/2D +3VRr3ItwsFIvW9exTuk0ENkw9CkZVp5Q7juf3CH62hLbeLGTPNphJheCA+R+yKsi +lJQVfIfauhHDEc+jFNId2ssUjbU7ktdw6xFhr4N0lT5UPCjp+wn0UZCwvdJeHIVy +iV7yXqeKvP4WRhSuAz23KhLO6m2t0bMJZv4ZuiHI9XdlIX7ePeNIX6JfCCTn1qeb +MeF3rGD/DuWM7elWXL9voViq1NVw9p/+SrMjitJyPnz+nupfWh1NdNZiqEJXQiru +n4wrck5Q8FggCBxteqZgQhi0u+pJBt+F/CMq4Tyof+i4qaNZUGUgWW9WnB7SvIaf +NYq27GydgaGIQp8AOi0wJ0g7fHkhuwNy7W6J4zVIcR+jKCzesYSY3RJlrOp0gdJn +h3nzk0a64r6aTHMCTP5KrbxWWZWOu8sXOjBofN3yGvvQcwlyyRpxuMJT9GYduRYp +gOzVftSsoDevF0jjNeqAN85kHQnzTd7D6cQDW8RJt+/nV+SmuOXSRZuFLHnl+abi +WIFNedr5zfHbbhZRDgsNVHU/Z7MtBjHypt5Ketg1HNxAyvdcd6Gf7g== +-----END CERTIFICATE----- diff --git a/specification/documentation/certs/prod-ca.crt b/specification/documentation/certs/prod-ca.crt new file mode 100644 index 000000000..4ed01c9b9 --- /dev/null +++ b/specification/documentation/certs/prod-ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQH91Zw4byissn0eyy+Cj85jANBgkqhkiG9w0BAQsFADBM +MQswCQYDVQQGEwJHQjEUMBIGA1UECgwLTkhTIEVuZ2xhbmQxJzAlBgNVBAMMHm5o +cy1tYWluLWFjY3QtY2xpZW50LWNhbGxiYWNrczAeFw0yNjA5MTUxMDMyMDNaFw0z +NjA5MTUxMTMyMDNaMEwxCzAJBgNVBAYTAkdCMRQwEgYDVQQKDAtOSFMgRW5nbGFu +ZDEnMCUGA1UEAwwebmhzLW1haW4tYWNjdC1jbGllbnQtY2FsbGJhY2tzMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAk7H5/iAWUSBgQV/xfVlNTNdcjrJk +UCMt0n70zgi+QQsH6AoAfRHX0Dr6zF+lCLH0OtGXAzuHq68okN9SkYEfXo93bpVe +DfR3dcLjNoyGiIiwILpHJ+NNNvRbYLXJez4c7fbJBz80Z+mcMR8rGrrFzTldZJ9S +EqGmS6PXtFUQPdAFkUlbyp9nstJSUTF33mCYMYTbAA1//0Pd+ZKXGCVMv6V6eTqX +qpkI26zVz9DJ7W28eyMbwmn0433OcKRK+0kblvUDKx0tUqpxgTUeeiDf20EzahtA +sgjmClTKW+G4xUmqdTOUsJaNbANdCG3eqnK2mOO3/ORrLi2LnG16O42kH7QINSA9 +Y1ChzFSsFixS8DeAMgQcHsXOWPOP4KJZghQqQHJNCHA3hBNS2/oQxtb01N2QQHAy +VIWansf0Yh/dNSqIvbKMpjfZplbDwMh45s16ejumtE9QnAuz18oxyZLHvFB6Bqyk +0O3hFaOu4WN5jP42mL8vmTBhEwFszhK1AqsZHM/rJYySPmUDqDfSSb9XMwaRYdHs +k7cBpXWWvcC8SxZ5zqthlz36avBjk5fCLJwLIylNk2g/RDBnEJo0cRLOuIKvYUlx +kIvbuGnGbn439V0RSMxihV5BmH8mL6DacsMbsvSWnQDkxMjiBcqshNsqI/+5FNIh +ItBYJ4GWjABmYbcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +JuqMlb8KGzZn8EPyaOHz9al9x2gwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4ICAQBUbd8ypPD/0x/wR5/4TzwtSeOwMYzdDWd6HaERGJBHabwpuUkxFz/U ++r5lMcbt7hhpK4djhQCfzkNX73zhJlauwt41b+btvpAtQjQp4sWgSzBZRaGLsM7r +BYQTjyk8cj8NZdGCj0ydRIEYNDI2OPYeB2gCWVrEMvr/6gOTlDDVJbY+bCk1H3zZ +fy9z5R+qohMEbf6fOFJT9vPvCRYJcIgmhn3Hs3cNfjOvj4Y0Gryqk0m71L4EPlB4 +6vuC43w9Saq7cmi9CVxf1pIKD66971HcoyGc2KQFzGO0Y9jX4EQFZRtVTS2W8+p+ +lQwh7IQd7kks7U1YxzBKt9Eeqe2ZD6hPU2/Gx4UhVoBdNliBCka8aUYv2ac+Zl6w +ogAAYv7ssbru6VNcQwEaJHbyN6RmIN2lOHNb+lQStBad10ZQRwoPOhJe13ASy0iP +ok5gR6GYKgUOw9cV0rlLlCagZzjrQRZNiOdyw+JnU9kRh3tGreSwWmhRZW/4Pc2n +Fb1LuC3J83HdA7RGuiiVvE95IxHp2TV2GXTdvNQEAMw8MtPFOroC+7NoiXHsEZ0e +jH/8khSoFUuZagTMYM70zL4gH4kVL13a7GlbUVEriPzjGR6bTROMk/uIVUy5gKc1 +sVmE0Q7kkQGyIs4sEQiQrYkOief8o56ts+wo6RuODP8fg+kcqH3PLQ== +-----END CERTIFICATE----- From 6f263c1a0a8f9593b98c1a820ab6a2481629a8f1 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Fri, 25 Sep 2026 08:25:00 +0100 Subject: [PATCH 3/5] wording tweaks --- specification/documentation/APIDescription.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specification/documentation/APIDescription.md b/specification/documentation/APIDescription.md index 73ecf9a1d..81b940aed 100644 --- a/specification/documentation/APIDescription.md +++ b/specification/documentation/APIDescription.md @@ -209,13 +209,13 @@ Our new callbacks mechanism uses Mutual TLS (mTLS) to verify the autheticity of Your service must request the client certificate during the callback and you must verify the following: -1. **Chain of trust**: the certificate be signed by the correct CA for the environment (see table below). -2. **Certificate expiry**: ensure that the current date and time is not outside `notBefore` and `notAfter`. +1. **Chain of trust**: the certificate must be signed by the correct CA for the environment (see table below). +2. **Certificate expiry**: the current date/time must not fall outside `notBefore` and `notAfter`. -In addition, we recommend that you also verify the following fields: +We also recommend that you also verify the following fields: -* The DN Common Name (CN) is: `NHS Notify Callbacks` -* The Subject Alternative Name (SAN) is correct for the environment (see table below) +* The DN Common Name (CN) should be `NHS Notify Callbacks` +* The Subject Alternative Name (SAN) URI should be the correct value for the environment (see table below) | Environment | CA Certificate | SAN URI | | --- | --- | --- | From 17d2bdfaba6e6fc3aebb2f7a203f24d49b9f30b8 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Fri, 25 Sep 2026 11:22:18 +0100 Subject: [PATCH 4/5] remove old callbacks header information from message & channel status change specs --- specification/callbacks/channel_status.yaml | 17 ++--------------- specification/callbacks/message_status.yaml | 14 -------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/specification/callbacks/channel_status.yaml b/specification/callbacks/channel_status.yaml index e1eaa935a..69b3a2379 100644 --- a/specification/callbacks/channel_status.yaml +++ b/specification/callbacks/channel_status.yaml @@ -24,20 +24,7 @@ description: |- - need to know exactly how a message has performed in real time with each of your recipients - need to know what happened to a message sent using a secondary cascade (opens in a new tab) operationId: post-v1-channel-callbacks -parameters: - - name: x-hmac-sha256-signature - in: header - description: Contains a HMAC-SHA256 signature of the request body using a - pre-agreed secret - schema: - type: string - example: 9ee8c6aab877a97600e5c0cd8419f52d3dcdc45002e35220873d11123db6486f - - name: x-api-key - in: header - description: Contains the pre-agreed API key. - schema: - type: string - example: 0bb04a0e-d005-42dd-8993-dacf37410a12 +security: [] requestBody: content: application/vnd.api+json: @@ -57,4 +44,4 @@ responses: '403': description: Forbidden '429': - $ref: ../responses/4xx/callbacks/429_TooManyRequests_Callbacks.yaml \ No newline at end of file + $ref: ../responses/4xx/callbacks/429_TooManyRequests_Callbacks.yaml diff --git a/specification/callbacks/message_status.yaml b/specification/callbacks/message_status.yaml index f7b2557d1..e80d9048a 100644 --- a/specification/callbacks/message_status.yaml +++ b/specification/callbacks/message_status.yaml @@ -22,20 +22,6 @@ description: |- If you need additional real time updates when the status of a channel changes, you can also use the channel and supplier status callback. operationId: post-v1-message-callbacks -parameters: - - name: x-hmac-sha256-signature - in: header - description: Contains a HMAC-SHA256 signature of the request body using a - pre-agreed secret - schema: - type: string - example: 9ee8c6aab877a97600e5c0cd8419f52d3dcdc45002e35220873d11123db6486f - - name: x-api-key - in: header - description: Contains the pre-agreed API key. - schema: - type: string - example: 0bb04a0e-d005-42dd-8993-dacf37410a12 requestBody: content: application/vnd.api+json: From 99bb17fe5e8c42e9dea6fd37755adafea8d00c53 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Fri, 25 Sep 2026 11:28:13 +0100 Subject: [PATCH 5/5] wording tweak --- specification/documentation/APIDescription.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/documentation/APIDescription.md b/specification/documentation/APIDescription.md index 81b940aed..e86a91c9c 100644 --- a/specification/documentation/APIDescription.md +++ b/specification/documentation/APIDescription.md @@ -207,7 +207,7 @@ We have created an OpenAPI specification detailing the behaviour of the endpoint Our new callbacks mechanism uses Mutual TLS (mTLS) to verify the autheticity of the callback to ensure that it has come from NHS Notify. -Your service must request the client certificate during the callback and you must verify the following: +Your service must request the client certificate (during the callback connection TLS negotiation) and must verify the following: 1. **Chain of trust**: the certificate must be signed by the correct CA for the environment (see table below). 2. **Certificate expiry**: the current date/time must not fall outside `notBefore` and `notAfter`.