Understanding NHS MESH API Authorization header and NHS MESH API pseudocode

Written by Technical Team Last updated 27.09.2025 14 minute read

Home>Insights>Understanding NHS MESH API Authorization header and NHS MESH API pseudocode

If you work with NHS integrations, you’ll encounter the Message Exchange for Social Care and Health (MESH) sooner rather than later. MESH is the national, secure “post-box” for moving clinical and administrative files between organisations. It’s robust, built for very large payloads, and, crucially, it’s designed to be consumed by software rather than people. The linchpin of a successful MESH integration is getting the Authorization header right. Get that wrong and every subsequent call—sending a file, polling an inbox, acknowledging a delivery—will fail.

This article walks through the MESH Authorization header in depth and then moves on to language-agnostic pseudocode you can adapt to your stack. It’s written for engineers who want to go beyond copying and pasting snippets to actually understand what’s going on, why it’s designed that way, and how to implement it cleanly and safely in production.

The MESH Authorization header: what it is and why it exists

At its heart, the MESH API uses a custom HMAC-based scheme carried in the standard HTTP Authorization header. You’ll see it advertised as the NHSMESH scheme (note the trailing space in the scheme token), and it’s used in combination with mutual TLS to identify your mailbox and protect requests against tampering and replay. Put simply: the TLS client certificate authenticates the connection; the Authorization header authenticates the request and binds it to your mailbox identity and the current moment in time.

Why both mTLS and an HMAC header? In NHS infrastructure, mTLS ensures you are an approved client presenting a valid certificate issued for the appropriate environment (INT, PTL, Live). But certificates alone don’t give you request-level integrity or strong replay protection. The HMAC signature solves that, ensuring each request is provably yours, tied to your mailbox credentials, and unique in time.

The design borrows from patterns you’ll recognise in other HMAC-signed APIs: a shared secret, a canonical string built from predictable elements, and a cryptographic hash that the server re-computes to verify your claim. The novelty for MESH is in the exact fields and their order, which you must mirror precisely to generate a valid signature. Small deviations—extra separators, wrong timestamp granularity, omitted fields—will invalidate the signature.

Dissecting the NHSMESH header: fields, timestamp and signature

The Authorization header value for MESH uses the custom scheme token NHSMESH followed by five colon-separated elements. When rendered, it looks like this:

Authorization: NHSMESH <mailbox_id>:<nonce>:<nonce_count>:<timestamp>:<signature>

Each element has a specific role and validation rule. In broad strokes:

  • NHSMESH – The literal scheme name with a trailing space; many parsing bugs come from omitting this space.
  • mailbox_id – Your assigned MESH mailbox (for example, X26OT282 in INT or your production mailbox ID).
  • nonce – A unique identifier per request. Most implementations use a GUID/UUID v4 string.
  • nonce_count – An integer that lets you disambiguate rapid, repeated requests using the same nonce. It’s commonly 0 unless you’re intentionally sending multiple attempts.
  • timestamp – A time value formatted as yyyyMMddHHmm (year to minute). Keep your clocks in sync with NTP; signatures quickly expire if your clock drifts.
  • signature – A hex (lowercase) HMAC-SHA256 digest computed from a canonical message and a shared key.

Under the bonnet, the canonical message you sign is:

<mailbox_id>:<nonce>:<nonce_count>:<password>:<timestamp>

…and the HMAC-SHA256 key is your shared secret for that mailbox in the current environment. The password is your mailbox password (distinct from the shared key). That’s an unusual but deliberate choice: the message binds both mailbox identity and your credential at the time of signing, and the server verifies the combination using the registered shared key. The end result is that a valid signature proves you knew the shared key and the password at the time the request was made.

A couple of non-obvious but important points about uniqueness and timing:

  • Uniqueness via nonce/nonce_count – The platform expects the header value to be unique per request. If you ever need to re-send the same HTTP request (for example, after a transient network failure), increment nonce_count and resign. This helps the service to cleanly reject replays without tripping you up during safe retries.
  • Timestamps at minute precision – Because the timestamp uses minutes (not seconds), a header is “fresh” for a brief window. If your client queues requests or the system clock is off by more than a minute, the service will treat your signature as stale. In practice, NTP plus signing immediately before dispatch prevents almost all issues.

Pseudocode for generating a valid MESH Authorization header

This section gives you language-agnostic pseudocode for generating the header consistently across stacks. You can translate it into C#, Java, Python, Node.js, Go or Rust with minimal friction.

Inputs you must have at runtime

  • mailbox_id – Your MESH mailbox.
  • mailbox_password – The password for that mailbox.
  • shared_key – The HMAC key for signing (environment-specific).
  • now() – Current time (synchronised via NTP), formatted as yyyyMMddHHmm.

Algorithm overview

  • Generate a nonce (UUID v4 string).
  • Set nonce_count = 0 (or increment if retrying the same request).
  • Build the canonical message msg = mailbox_id + “:” + nonce + “:” + nonce_count + “:” + mailbox_password + “:” + timestamp.
  • Compute signature = HMAC_SHA256(key = shared_key, data = msg), encode as lowercase hex.
  • Emit the header value NHSMESH + mailbox_id:nonce:nonce_count:timestamp:signature.

Pseudocode

function build_mesh_authorization(mailbox_id, mailbox_password, shared_key):
nonce = uuid_v4() // e.g., “6f732ccc-8c6f-4c9b-a22d-2f42978550ad”
nonce_count = 0 // increment when retrying an identical request
timestamp = format(now(), “yyyyMMddHHmm”) // minute precision
message = mailbox_id + “:” + nonce + “:” + str(nonce_count) + “:” + mailbox_password + “:” + timestamp
signature = hex( HMAC_SHA256(shared_key, message) ).lower()
return “NHSMESH ” + mailbox_id + “:” + nonce + “:” + str(nonce_count) + “:” + timestamp + “:” + signature

If you need to retry the exact same HTTP request (same method, URL and body) due to a transient failure, keep the nonce but increment nonce_count before re-signing:

function retry_header(original_nonce, nonce_count, mailbox_id, mailbox_password, shared_key):
new_count = nonce_count + 1
timestamp = format(now(), “yyyyMMddHHmm”)
message = mailbox_id + “:” + original_nonce + “:” + str(new_count) + “:” + mailbox_password + “:” + timestamp
signature = hex(HMAC_SHA256(shared_key, message)).lower()
return “NHSMESH ” + mailbox_id + “:” + original_nonce + “:” + str(new_count) + “:” + timestamp + “:” + signature

Common pitfalls that cause authentication failures

  • Using the wrong key for the HMAC (for example, the mailbox password instead of the shared key).
  • Dropping the trailing space in the NHSMESH scheme token.
  • Using uppercase hex for the digest, or base64 instead of hex.
  • Formatting the timestamp incorrectly or letting clocks drift—configure NTP and sign immediately before sending.
  • Changing the canonical message order or missing the password element when building the string to sign.

MESH API pseudocode: sending, receiving and acknowledging messages

With a valid header generator in hand, the rest of your integration becomes straightforward. The MESH API is deliberately simple: send a message to a recipient mailbox (or use workflow-based routing), poll your inbox to find new messages, download each one, then acknowledge to delete it from your inbox. The service acts like a secure, reliable, store-and-forward post-box.

Sending a message

At send time you post to your outbox endpoint and include a few MESH metadata headers alongside your Authorization header. Different workflows will expect different header combinations, but the common ones you’ll see are:

  • Mex-From – your mailbox ID
  • Mex-To – the recipient mailbox ID (or a patient-based routing string where supported)
  • Mex-Subject – human-readable subject line, often workflow-specific
  • Mex-Localid – your unique identifier for this message/batch
  • Mex-WorkflowID – the workflow that drives routing/processing

Some national services also publish workflow-specific guidance describing the exact header set they expect. The names are case-insensitive on the wire, but it’s wise to stick to their published casing.

Pseudocode: send

function mesh_send_file(base_url, mailbox_id, mailbox_password, shared_key, file_bytes, headers):
// base_url sample: “https://msg.int.spine2.ncrs.nhs.uk/messageexchange”
auth_header = build_mesh_authorization(mailbox_id, mailbox_password, shared_key)

url = base_url + “/messageexchange/” + url_encode(mailbox_id) + “/outbox”
http_headers = {
“Authorization”: auth_header,
“Content-Type”: “application/octet-stream”,
“Mex-From”: mailbox_id,
“Mex-To”: headers[“to”],
“Mex-Subject”: headers[“subject”],
“Mex-Localid”: headers[“local_id”],
“Mex-WorkflowID”:headers[“workflow_id”]
}

response = HTTP.POST(url, headers=http_headers, body=file_bytes)
assert response.status in [200, 201, 202]
return response.json() or response.headers

For very large files, you’ll typically switch to chunked upload. The flow initiates a message, uploads sequential chunks, and then finalises. The pattern is analogous to multi-part upload in cloud storage: you repeat the same signing logic per HTTP request, but the body and content headers change to carry the chunk number and size. (Exact chunking parameters are subject to the current API specification; the core signing approach remains the same.)

If you don’t know the recipient mailbox ID, you can use the Endpoint Lookup Service with an Organisation Data Service (ODS) code and a workflow to discover the proper recipient mailbox for your use case, then address your send accordingly.

Polling your inbox

The inbox model is predictable: list the messages, pick one, download the payload, and then acknowledge. You can poll at a sensible interval (for example, once a minute or tied to a queue consumer) and scale out consumers if your volume is high.

Pseudocode: list & download

function mesh_list_inbox(base_url, mailbox_id, mailbox_password, shared_key):
auth_header = build_mesh_authorization(mailbox_id, mailbox_password, shared_key)
url = base_url + “/messageexchange/” + url_encode(mailbox_id) + “/inbox”
headers = { “Authorization”: auth_header, “Accept”: “application/json” }
response = HTTP.GET(url, headers=headers)
assert response.status == 200
return response.json()[“messages”]

function mesh_download_message(base_url, mailbox_id, mailbox_password, shared_key, message_id):
auth_header = build_mesh_authorization(mailbox_id, mailbox_password, shared_key)
url = base_url + “/messageexchange/” + url_encode(mailbox_id) + “/inbox/” + url_encode(message_id)
headers = { “Authorization”: auth_header, “Accept”: “application/octet-stream” }
response = HTTP.GET(url, headers=headers)
assert response.status == 200
return {
“payload”: response.body_bytes,
“headers”: response.headers
}

When you receive a message, the HTTP response also includes the MESH metadata headers that the sender supplied (subject, workflow ID, local ID, and so on). You’ll typically use these to route internally: for example, workflow ID → handler, local ID → idempotency key, subject → audit log.

Acknowledging (and thereby removing) a message

Once you’ve durably stored and processed the file, acknowledge the message. MESH deletes it from your inbox when the ACK succeeds, preventing re-processing. You should treat this like a message queue: only ACK after your processing is complete and committed.

Pseudocode: acknowledge

function mesh_acknowledge(base_url, mailbox_id, mailbox_password, shared_key, message_id):
auth_header = build_mesh_authorization(mailbox_id, mailbox_password, shared_key)
url = base_url + “/messageexchange/” + url_encode(mailbox_id) + “/inbox/” + url_encode(message_id) + “/acknowledge”
headers = { “Authorization”: auth_header }
response = HTTP.POST(url, headers=headers)
assert response.status in [200, 204]

Tracking sent message status

After sending, you can query for delivery status—whether the message has been collected by the recipient, or if a non-delivery report (NDR) has been raised because the recipient didn’t collect within the retention period. This lets you build clear operational views (“awaiting pickup”, “collected”, “expired/failed”) and trigger follow-ups if needed. The high-level pattern is identical: construct a fresh Authorization header, call the status endpoint, and interpret the result.

Testing, troubleshooting and security hardening for production

Even with a perfect algorithm, reality bites: clocks drift, proxies mangle headers, and staging secrets leak into local configs. This final section collects hard-won practices to help your MESH integration behave like a good citizen in NHS environments.

Validate your header locally before plumbing it into HTTP

Use a local or online header validator that can generate or verify the NHSMESH header offline. It’s handy when debugging a failing call: compute the expected signature locally, compare it to what your client produced, and look for differences in the canonical string or key choice. If they deviate, fix your assembly code before touching transport.

Use a sandbox and test mailboxes

Before you try INT or PTL, bring up a sandbox that emulates the MESH API. You can exercise send/poll/ack flows, inspect headers, and simulate large-file chunking without touching national infrastructure. When you graduate to INT, request a test mailbox, shared key and certificate for that environment and confirm everything under mTLS.

Keep time in lockstep and sign at the last possible moment

Because the timestamp granularity is minutes, the safest pattern is: read the clock, assemble the canonical message, sign, then dispatch immediately. Don’t pre-sign batches or carry pre-signed requests across process boundaries—they’ll age out and fail. Run NTP on every host (or your container nodes) and log both client and server times when diagnosing failures.

Separate and rotate secrets

Treat the shared key and mailbox password as distinct credentials in your secret store. Rotate them independently. Remember that the shared key is the HMAC key; the password goes inside the canonical message. Don’t log either in plaintext—mask both in application logs and structured traces. (If you ever must log the canonical string during deep debugging, ensure your redaction logic removes the password segment before emitting the line.)

Harden transport and header handling

  • Verify you’re presenting the correct client certificate for the environment (INT vs PTL vs Live) and that your TLS stack sends the full chain expected by the gateway.
  • Ensure any reverse proxy in front of your app doesn’t strip or rewrite the Authorization header. Some HTTP stacks drop unknown schemes by default. Configure explicit pass-through rules.
  • If you terminate TLS upstream (for example, on a load balancer), consider terminating mTLS at the application edge instead so your service has direct access to the client certificate context for auditing.

Right-size your poll loop and implement backoff

MESH isn’t a pub/sub stream; it’s a mailbox. Poll at a steady rate appropriate to your SLA and back off when inbox empty responses are frequent. During spikes, scale horizontally (parallel downloaders) but respect limits. If you stage messages for downstream systems, introduce a durable internal queue and ACK only when downstream persistence succeeds.

Establish idempotency and replay resilience

On the send side, use Mex-Localid as an idempotency key: if a network hiccup causes your HTTP client to retry, the receiving system can de-duplicate based on that local reference. On the receive side, process messages in a store-then-process pattern: persist the payload and MESH headers, compute a content hash, and only then hand off to business logic. That way you can withstand app restarts mid-flow without losing the ability to ACK confidently.

Build strong observability around MESH calls

Log the nonce, nonce_count, and timestamp alongside the mailbox ID and endpoint path for every request (but never the shared key or password). When you get a 401/403, you’ll immediately see whether you re-used a header or introduced a clock skew. For 5xx or timeouts, retry the HTTP request but increment nonce_count and re-sign so the platform doesn’t read your attempt as a replay.

Follow workflow-specific guidance

The base protocol is the same for all MESH users, but individual national services often publish extra guidance—required headers, subject formats, routing constraints, or service-level behaviours. If you’re integrating with such a service, adopt their workflow ID and header recipes rather than inventing your own. This keeps you interoperable and makes support conversations much shorter.

Final guidance and MESH integration implementation checklist

Before you push to production, walk through this checklist and make it part of your team’s runbook:

  • Maintain a single, audited module that builds and returns the NHSMESH header, covered by unit tests that pin canonical examples.
  • Keep clock sync tight and sign immediately before dispatch.
  • Store mailbox password and shared key separately in your secret manager; rotate on a schedule and upon staff changes.
  • For retries, reuse the nonce, increment nonce_count, re-sign, and resend.
  • Capture nonce, nonce_count, timestamp, and the endpoint path in your logs for each call; mask secrets at the logger boundary.
  • Treat Mex-Localid as an idempotency key: enforce uniqueness on the sender side; de-duplicate on the receiver side.
  • Validate your implementation against a local sandbox and, if available, a header validator before calling national environments.

With these foundations in place, you’ll find that the MESH API is not only predictable but pleasantly minimal. The signing routine is a few lines of code, the message lifecycle is obvious, and the operational behaviours follow the intuitions of a queue with explicit acknowledgements. From there, your focus can shift to the interesting bits—what you send, how you route it, and how quickly you can turn those files into patient-centred value.

Need help with MESH integration?

Is your team looking for help with MESH integration? Click the button below.

Get in touch