> ## 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.

# Authentication

> Secure your API requests using HMAC SHA-256 signatures with pre-shared API keys, including step-by-step instructions and code examples.

* All API calls use an HMAC authentication strategy with pre-shared API and secret keys.
* Sandbox and production environments have different API credentials. Do not reuse across environments.
* All requests must use HTTPS (TLS 1.2 or higher).
* You must allow outbound connections to the Payout Origination API base URLs.
* IP whitelisting may be required for production access depending on your program configuration. Meridian provides details if applicable.

Every request must include the following headers:

| Header                  | Description                                                             |
| :---------------------- | :---------------------------------------------------------------------- |
| `X-Meridian-Api-Key`    | Issued by Meridian                                                      |
| `X-Meridian-Program-Id` | Identifies your Meridian program                                        |
| `X-Meridian-Timestamp`  | Current time in milliseconds (must be within 60 seconds of the request) |
| `X-Meridian-Signature`  | HMAC SHA-256 signature                                                  |

<Info>
  Meridian issues your API Key and Secret during Sandbox and production provisioning.
</Info>

When Meridian receives the request, it validates the signature. If the signature does not match, the API immediately returns a `401 Unauthorized` response.

## Constructing the signature

<Steps>
  <Step title="Build the canonical string">
    Compute the signature as an HMAC SHA-256 hash over a canonical string that concatenates the following values in order:

    | Signature component | Notes                                                                                                                                                         |
    | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `API-Key`           | Same value as `X-Meridian-Api-Key`                                                                                                                            |
    | `Timestamp`         | Same value as `X-Meridian-Timestamp`                                                                                                                          |
    | `Request method`    | Example: `GET`, `POST`, `PATCH`                                                                                                                               |
    | `Request path`      | Including any parameters. For example:<br />`/v1/payout-origination/{format}`, `/v1/transactions`, or `/v1/transactions/txn_01JVY8Y4N9X2M6S5Q7T1`             |
    | `Body contents`     | The exact request body sent on the wire. For Payout Origination POST requests, this is the encoded payload. If there is no body, this can be an empty string. |

    `X-Meridian-Program-Id` is required on every request but is not included in the canonical signing string.
  </Step>

  <Step title="Compute the HMAC">
    Compute the HMAC using the language-appropriate crypto library with the secret key.

    <Warning>
      Make sure you compute the HMAC with the API Secret that Meridian issued to you.
    </Warning>
  </Step>

  <Step title="Compute the SHA-256 hash">
    Hash the final value using the SHA-256 algorithm and set it as the `X-Meridian-Signature` header.

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

      fun buildMeridianSignature(apiKey: String, apiSecret: String, programId: String, method: String, path: String, body: String = ""): Map<String, String> {
          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 mapOf(
              "X-Meridian-Api-Key" to apiKey,
              "X-Meridian-Program-Id" to programId,
              "X-Meridian-Timestamp" to timestamp,
              "X-Meridian-Signature" to signature,
              "Content-Type" to "text/plain"
          )
      }
      ```

      ```swift swift theme={null}
      import CryptoKit
      import Foundation

      func buildMeridianSignature(apiKey: String, apiSecret: String, programId: String, method: String, path: String, body: String = "") -> [String: String] {
          let timestamp = String(Int(Date().timeIntervalSince1970 * 1000))
          let canonical = apiKey + timestamp + method + path + body
          let key = SymmetricKey(data: Data(apiSecret.utf8))
          let signature = HMAC<SHA256>.authenticationCode(for: Data(canonical.utf8), using: key)
              .map { String(format: "%02x", $0) }.joined()

          return [
              "X-Meridian-Api-Key": apiKey,
              "X-Meridian-Program-Id": programId,
              "X-Meridian-Timestamp": timestamp,
              "X-Meridian-Signature": signature,
              "Content-Type": "text/plain"
          ]
      }
      ```

      ```javascript postman theme={null}
      // ─── Config ──────────────────────────────────────────────────────────────────
      // Set these as collection variables (never hardcode secrets)
      const apiKey    = pm.variables.get("meridianApiKey");
      const apiSecret = pm.variables.get("meridianApiSecret");

      // ─── Timestamp (milliseconds) ─────────────────────────────────────────────────
      const timestamp = Date.now().toString();

      // ─── Request components ───────────────────────────────────────────────────────
      const method = pm.request.method; // e.g. "POST"

      const url = pm.request.url;
      const qs  = url.getQueryString();
      const path = url.getPath() + (qs ? "?" + qs : ""); // e.g. "/v1/payout-origination/pacs008" or "/v1/transactions"

      const body = pm.request.body?.raw ?? ""; // raw Base64 payload for POST requests

      // ─── Canonical string ─────────────────────────────────────────────────────────
      const canonical = apiKey + timestamp + method + path + body;

      // ─── HMAC-SHA256 signature ────────────────────────────────────────────────────
      const signature = CryptoJS.HmacSHA256(canonical, apiSecret).toString(CryptoJS.enc.Hex);

      // ─── Inject into request headers ─────────────────────────────────────────────
      pm.variables.set("meridianTimestamp", timestamp);
      pm.variables.set("meridianSignature", signature);
      pm.request.headers.add({ key: "X-Meridian-Program-Id", value: pm.variables.get("meridianProgramId") });

      // ─── Debug ────────────────────────────────────────────────────────────────────
      console.log("Timestamp :", timestamp);
      console.log("Canonical :", canonical);
      console.log("Signature :", signature);
      ```
    </CodeGroup>
  </Step>
</Steps>

## Common errors

<Warning>
  Meridian returns 401 Unauthorized for requests with missing or invalid signatures.
</Warning>

The most common causes are:

* Canonical string fields concatenated in the wrong order.
* Timestamp too far from the server's current time.
* Forgetting to compute the HMAC with your unique Secret Key.
* Signing a different Base64 string than the one actually sent.
* Forgetting to SHA-256 hash the final string.
* Including `X-Meridian-Program-Id` in the canonical string.
