Ovianta Developers

Authentication

What the X-Signature header proves, what it does not, and how to protect your API.

Every request Ovianta sends to your agenda API carries an X-Signature header. It proves that a request body was written by someone who has the integration's secret. It does not, on its own, prove that a whole request comes from Ovianta: read What the signature does not cover before relying on it.

The signature

X-Signature is the HMAC-SHA256 of the raw request body, keyed with the integration's secret and encoded as lowercase hex. Requests without a body (GET and DELETE) sign an empty string.

POST /ovianta/appointments HTTP/1.1
Content-Type: application/json
Accept: application/json
X-Signature: 3c5e1f0a8b7d...

{"date":"16/02/2026","time":"16:00","doctorId":"24895","patientId":"345345"}

To check it:

  1. Read the body exactly as received, before any JSON parsing. Use an empty string when there is none.
  2. Compute the HMAC-SHA256 of it with the secret, as hex.
  3. Compare it with X-Signature using a constant-time comparison.
  4. Reject the request with 401 if they differ.

What the signature does not cover

The signature covers the body and nothing else. In particular:

  • The method, path and query string are not signed. A valid signature for one request is also valid for the same body sent to a different endpoint.
  • Requests without a body all have the same signature. Every GET and DELETE signs an empty string, so for a given secret their X-Signature is always the same value. Anyone who sees one lookup's signature can send a DELETE /appointments/{appointmentId} with a valid signature.
  • There is no timestamp, so there is no replay protection. A captured request can be sent again later and still verify.

So treat a valid signature as proof that a body was not changed, not as permission to act. Protect the API with controls that cover the whole request:

  • Serve it only over HTTPS, so requests and their signatures cannot be read on the way.
  • Keep the base URL private. Do not publish it, and use a path that cannot be guessed.
  • Restrict who can reach it, for example by exposing it only through a relay tunnel.
import crypto from 'node:crypto';

export function isFromOvianta(rawBody: string, signature: string | undefined, secret: string): boolean {
  if (!signature) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const received = Buffer.from(signature, 'hex');
  return received.length === expected.length / 2 && crypto.timingSafeEqual(received, Buffer.from(expected, 'hex'));
}

Sign the raw bytes

Parsing the JSON and serializing it again changes spacing and key order, and the signature will not match.

Relay tunnels

If your API is not reachable from the internet, Ovianta can reach it through an Azure Relay hybrid connection. You give Ovianta the relay's connection string, and every request also carries a ServiceBusAuthorization header with a short-lived SAS token for the relay. The relay checks that header; X-Signature still reaches your API unchanged.

On this page