> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mnai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-to-server authentication with HMAC

> Use Meridian partner credentials and an HMAC SHA-256 signature when your backend calls the Meridian Accounts API directly.

Use partner HMAC authentication when your backend calls Meridian directly with a Meridian API Key and Secret.

This is the mechanism that proves partner identity. It is required for `POST /v1/auth/token` and for any other API operation that supports partner-key authentication.

## Required headers

Partner-authenticated requests use these headers:

| Header                  | Required          | Use                                                            |
| :---------------------- | :---------------- | :------------------------------------------------------------- |
| `X-Meridian-Api-Key`    | Yes               | Issued by Meridian for the environment                         |
| `X-Meridian-Program-Id` | Yes               | Identifies the program context for the request                 |
| `X-Meridian-Timestamp`  | Yes               | Timestamp used to validate request freshness and the signature |
| `X-Meridian-Signature`  | Yes               | HMAC SHA-256 signature for the request                         |
| `X-Meridian-User-Id`    | `MULTI_USER` only | Identifies the Meridian user targeted by the request           |

For `SINGLE_USER` integrations, omit `X-Meridian-User-Id`. Meridian uses the single configured user for that partner key.

## When to send `X-Meridian-User-Id`

Your integration model determines whether `X-Meridian-User-Id` is required:

* `MULTI_USER`: required on partner-key calls
* `SINGLE_USER`: must be omitted on partner-key calls

This rule applies to token creation and to other partner-key API calls that accept direct partner authentication.

## Constructing the signature

Meridian authenticates partner-key requests with an HMAC SHA-256 signature.

`X-Meridian-Program-Id` is required on the request, but it is not part of the canonical string used for signing.

### Canonical string

Build the canonical string by concatenating these values in this exact order:

1. `API Key`
2. `Timestamp`
3. `HTTP method`
4. `Request path`
5. `Request body`

For example, for `POST /v1/auth/token`:

* the method is `POST`
* the path is `/v1/auth/token`
* the body is the exact JSON string sent on the wire

If there is no request body, use an empty string for the body portion of the canonical string.

### How to compute the signature

1. Read your Meridian API Key and API Secret for the environment you are calling.
2. Generate a timestamp in milliseconds and send it as `X-Meridian-Timestamp`.
3. Serialize the JSON request body exactly as it will be sent.
4. Concatenate the API Key, timestamp, method, path, and serialized body to form the canonical string.
5. Compute the HMAC SHA-256 of that canonical string using your API Secret as the key.
6. Hex-encode the result and send it as `X-Meridian-Signature`.

### Example

If your request is:

```http theme={null}
POST /v1/auth/token
X-Meridian-Api-Key: mer_test_123
X-Meridian-Program-Id: prog_abc123
X-Meridian-User-Id: user_123
X-Meridian-Timestamp: 1735689600000
Content-Type: application/json
```

```json theme={null}
{ "grant_type": "client_credentials" }
```

Then the canonical string is:

```text theme={null}
mer_test_1231735689600000POST/v1/auth/token{"grant_type":"client_credentials"}
```

Compute the HMAC SHA-256 of that canonical string using your API Secret, then hex-encode the output and send the result in `X-Meridian-Signature`.

### Example code

<CodeGroup>
  ```javascript javascript theme={null}
  import crypto from "crypto";

  const apiKey = process.env.MERIDIAN_API_KEY;
  const apiSecret = process.env.MERIDIAN_API_SECRET;
  const programId = process.env.MERIDIAN_PROGRAM_ID;

  const method = "POST";
  const path = "/v1/auth/token";
  const timestamp = Date.now().toString();
  const body = JSON.stringify({
    grant_type: "client_credentials",
  });

  const canonical = apiKey + timestamp + method + path + body;

  const signature = crypto
    .createHmac("sha256", apiSecret)
    .update(canonical)
    .digest("hex");

  const headers = {
    "Content-Type": "application/json",
    "X-Meridian-Api-Key": apiKey,
    "X-Meridian-Program-Id": programId,
    "X-Meridian-User-Id": "user_123",
    "X-Meridian-Timestamp": timestamp,
    "X-Meridian-Signature": signature,
  };
  ```

  ```kotlin kotlin theme={null}
  import javax.crypto.Mac
  import javax.crypto.spec.SecretKeySpec

  fun buildMeridianTokenHeaders(
      apiKey: String,
      apiSecret: String,
      programId: String,
      userId: String?,
      body: String
  ): Map<String, String> {
      val method = "POST"
      val path = "/v1/auth/token"
      val timestamp = System.currentTimeMillis().toString()
      val canonical = apiKey + timestamp + method + path + body

      val mac = Mac.getInstance("HmacSHA256").apply {
          init(SecretKeySpec(apiSecret.toByteArray(), "HmacSHA256"))
      }

      val signature = mac.doFinal(canonical.toByteArray())
          .joinToString("") { "%02x".format(it) }

      return buildMap {
          put("Content-Type", "application/json")
          put("X-Meridian-Api-Key", apiKey)
          put("X-Meridian-Program-Id", programId)
          if (userId != null) {
              put("X-Meridian-User-Id", userId)
          }
          put("X-Meridian-Timestamp", timestamp)
          put("X-Meridian-Signature", signature)
      }
  }
  ```

  ```bash bash theme={null}
  API_KEY="mer_test_123"
  API_SECRET="your_api_secret"
  PROGRAM_ID="prog_abc123"
  USER_ID="user_123"
  TIMESTAMP="$(python3 -c 'import time; print(int(time.time() * 1000))')"
  BODY='{"grant_type":"client_credentials"}'
  CANONICAL="${API_KEY}${TIMESTAMP}POST/v1/auth/token${BODY}"

  SIGNATURE="$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" | sed 's/^.* //')"
  ```
</CodeGroup>

## Token minting flow

Use `POST /v1/auth/token` when your backend needs a Meridian access token for a user.

* `SINGLE_USER`: omit `X-Meridian-User-Id`; Meridian uses the user configured for that partner integration
* `MULTI_USER`: include `X-Meridian-User-Id` to identify the target Meridian user

Depending on your flow, the token request body can:

* use `client_credentials` for a target user
* use `refresh_token` to exchange a refresh token for a new access token

For a first-time `MULTI_USER` flow, create the Meridian user first with `POST /v1/auth/users`, then mint the token for that `userId`.

If `POST /v1/auth/users` fails because `externalId` already belongs to an existing Meridian user, retry token creation only if you already know the stored `userId` from your own customer-to-user mapping.

## Partner-key access beyond token minting

Some Meridian Accounts endpoints support direct partner-key authentication in addition to JWT bearer authentication. When the OpenAPI for an endpoint lists both partner API Key security and JWT security, your backend may call that endpoint directly with HMAC-signed partner credentials.

For client-facing account access, prefer JWT bearer authentication even when partner-key auth is technically supported.

## Common authentication errors

The most common causes of HMAC authentication failures are:

* omitting `X-Meridian-Api-Key` or another required partner header
* concatenating canonical string fields in the wrong order
* signing a different JSON string than the one actually sent
* including `X-Meridian-Program-Id` in the canonical string
* sending `X-Meridian-User-Id` for a `SINGLE_USER` integration
* omitting `X-Meridian-User-Id` for a `MULTI_USER` integration
* using a timestamp outside the allowed 60-second window
* using Sandbox credentials against production, or production credentials against Sandbox

<Warning>
  The signature is sensitive to field order, whitespace, and request path. If
  the request body or path differs from what you signed, Meridian will reject
  the request.
</Warning>

## Related guides

* [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model)
* [Authentication overview](/products/meridian-accounts/guides/authentication-overview)
* [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt)
