---
updatedAt: 2026-07-08T09:36:47.000Z
agentTools:
  projectIndex: https://doc.fintecture.com/llms.txt
---

# HTTP Signature

# Description

Some APIs require an HTTP signature for reinforced security. Your `app_private_key`, as defined in the console, will be required for this. The HTTP header follows the [signing HTTP Messages](https://datatracker.ietf.org/doc/draft-cavage-http-signatures/10/) IETF standard, with the following particularities:

* the **keyId** value is your `app_id`
* the only **algorithm** currently supported is `rsa-sha256`

All endpoints requesting HTTP signature will require following additional header parameters:

| Name         | Required                                                                               | Description               | Example                                                                                                                                                                                         |
| :----------- | :------------------------------------------------------------------------------------- | :------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| date         | Always                                                                                 | RFC 2822 formatted date   | Wed, 26 Feb 2020 17:29:51 GMT                                                                                                                                                                   |
| digest       | <a class="post">POST</a><br /> <a class="put">PUT</a> <br /><a class="patch">PATCH</a> | Hashed payload            | SHA-256=cjuagrzhZ8joOWLlQCCe5co30bRISL1VIWNq99da+hM=                                                                                                                                            |
| x-request-id | Always                                                                                 | UUID v4 identifier        | 123e4567-e89b-42d3-a456-426614174000                                                                                                                                                            |
| Signature    | Always                                                                                 | HTTP calculated signature | keyId="0354d723-d8d3-469a-8926-4f3f18b2c416",<br />algorithm="rsa-sha256",<br />headers="(request-target) date x-request-id",<br />signature="eyvAyh5kuqifP8vkUy5KBWPgtQAurB7xMeC6T/KGJQm2JA==" |

> 🚧 **Important**
>
> **Signature**, **digest** (if a POST, PUT or PATCH), **date** and **x-request-id** headers are optional in the SANDBOX environment but mandatory when calling the PRODUCTION one.

> 💡 **Note**
>
> An [interactive HTTP Signature guide](https://signature-guide.fintecture.com) has been created to help you through the process of creating the **digest** and **Signature** headers. You can also check out [request signature examples](https://github.com/Fintecture/signature-guide/tree/main/src/scripts) in different programming languages (JS, Java, Python...) on our GitHub project.

# Process

## 1. Build the message digest

The digest is a SHA-256 hash of the payload encoded into base64, and concatenated with a "SHA-256=" prefix:

```text
digest = "SHA-256=" + base64( SHA256( body ) )
```

> ❗️ **Critical**
>
> The **body** must be the exact raw JSON string sent in the request — **minified, with no extra whitespace**. Using pretty-printed JSON (e.g. `JSON.stringify(body, null, 2)`) will produce a different digest. Use `JSON.stringify(body)` instead.

> 💡 **Note**
>
> Make sure your body is encoded into UTF-8 with unescaped unicode to avoid bad surprises in case accents or other special characters are included in the body.

## 2. Create the signing parameters

| Name             | Description                                            | Example                                              |
| :--------------- | :----------------------------------------------------- | :--------------------------------------------------- |
| (request-target) | Method and pathname of an URL                          | get /ais/v1/customer/123/accounts                    |
| date             | An RFC 2822 formatted date                             | Wed, 26 Feb 2020 17:29:51 GMT                        |
| digest           | The SHA-256 digest of the body as described in point 1 | SHA-256=cjuagrzhZ8joOWLlQCCe5co30bRISL1VIWNq99da+hM= |
| x-request-id     | An UUID v4 formatted unique value                      | 123e4567-e89b-42d3-a456-426614174000                 |

## 3. Build the signing string

For <a class="get">GET</a> & <a class="delete">DELETE</a> requests, use:

* `(request-target)`
* `date`
* `x-request-id`

For <a class="post">POST</a>, <a class="put">PUT</a> & <a class="patch">PATCH</a> requests, use:

* `(request-target)`
* `date`
* `digest`
* `x-request-id`

<br />

```text
(request-target): get /ais/v1/customer/123/accounts?querystring=true\n
date: Wed, 26 Feb 2020 17:29:51 GMT\n
x-request-id: 123e4567-e89b-42d3-a456-426614174000
```

> 💡 **Note**
>
> Make sure the name of each parameter is lower-cased (not the value), there is a ": " between the name and the value, and a return character "\n" at the end of each line except the last one. For the (request-target), include query params in the pathname.

## 4. Encrypt the signing string

Sign the string with your private key and encode the result into base64:

```text
signature = base64( RSA-SHA256( signing string ) )
```

## 5. Create the signature string

Concatenate all fields separating them by a comma (",").

For <a class="get">GET</a> & <a class="delete">DELETE</a> requests:

```text
keyId=`app_id`,
algorithm=rsa-sha256,
headers=(request-target) date x-request-id,
signature=`signature`
```

For <a class="post">POST</a>, <a class="put">PUT</a> & <a class="patch">PATCH</a> requests:

```text
keyId=`app_id`,
algorithm=rsa-sha256,
headers=(request-target) date digest x-request-id,
signature=`signature`
```

This results in an HTTP signature with the following structure:

```text
keyId="0354d723-d8d3-469a-8926-4f3f18b2c416",algorithm="rsa-sha256",headers="(request-target) date digest x-request-id",signature="eyvAyh5kuqifP8vkUy5KBWPgtQAurB7xMeC6T/KGJQm2JA=="
```

# Complete example

The helper below implements the whole process: it computes the digest (for requests with a body), builds the signing string in the right order, signs it with your private key, and returns all four headers ready to merge into your request.

```javascript Node.js
const crypto = require('crypto');

function signedHeaders({ appId, privateKeyPem, method, path, body }) {
  const date = new Date().toUTCString();
  const xRequestId = crypto.randomUUID();

  const headers = { date, 'x-request-id': xRequestId };
  const lines = [`(request-target): ${method.toLowerCase()} ${path}`, `date: ${date}`];
  let signedList = '(request-target) date x-request-id';

  if (body !== undefined) {
    // Hash the exact minified string you will send as the request body
    const raw = JSON.stringify(body);
    const digest = 'SHA-256=' + crypto.createHash('sha256').update(raw, 'utf8').digest('base64');
    headers.digest = digest;
    lines.splice(2, 0, `digest: ${digest}`);
    signedList = '(request-target) date digest x-request-id';
  }

  lines.push(`x-request-id: ${xRequestId}`);
  const signingString = lines.join('\n');
  const signature = crypto
    .sign('sha256', Buffer.from(signingString, 'utf8'), privateKeyPem)
    .toString('base64');

  headers.signature = `keyId="${appId}",algorithm="rsa-sha256",headers="${signedList}",signature="${signature}"`;
  return headers;
}

// Usage — merge with your other headers (Authorization, Content-Type...)
const headers = signedHeaders({
  appId: process.env.FINTECTURE_APP_ID,
  privateKeyPem: process.env.FINTECTURE_PRIVATE_KEY,
  method: 'GET',
  path: '/ais/v1/customer/123/accounts?querystring=true',
});
```
```python Python
import base64
import hashlib
import json
import os
import uuid
from email.utils import formatdate

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding


def signed_headers(app_id, private_key_pem, method, path, body=None):
    date = formatdate(usegmt=True)  # RFC 2822, e.g. Wed, 26 Feb 2020 17:29:51 GMT
    x_request_id = str(uuid.uuid4())

    headers = {"date": date, "x-request-id": x_request_id}
    lines = [f"(request-target): {method.lower()} {path}", f"date: {date}"]
    signed_list = "(request-target) date x-request-id"

    if body is not None:
        # Hash the exact minified string you will send as the request body
        raw = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
        digest = "SHA-256=" + base64.b64encode(hashlib.sha256(raw.encode()).digest()).decode()
        headers["digest"] = digest
        lines.insert(2, f"digest: {digest}")
        signed_list = "(request-target) date digest x-request-id"

    lines.append(f"x-request-id: {x_request_id}")
    signing_string = "\n".join(lines)

    key = serialization.load_pem_private_key(private_key_pem.encode(), password=None)
    signature = base64.b64encode(
        key.sign(signing_string.encode(), padding.PKCS1v15(), hashes.SHA256())
    ).decode()

    headers["signature"] = (
        f'keyId="{app_id}",algorithm="rsa-sha256",'
        f'headers="{signed_list}",signature="{signature}"'
    )
    return headers


# Usage — merge with your other headers (Authorization, Content-Type...)
headers = signed_headers(
    app_id=os.environ["FINTECTURE_APP_ID"],
    private_key_pem=os.environ["FINTECTURE_PRIVATE_KEY"],
    method="GET",
    path="/ais/v1/customer/123/accounts?querystring=true",
)
```

> ❗️ **Send exactly what you signed**
>
> If you compute a digest from a body, the bytes you send on the wire must be that exact string. Serialize once, sign it, and send the same variable — don't let your HTTP library re-serialize the object.

# Troubleshooting

| Error                       | Cause                                                                                                               | Fix                                                                                                                                                                 |
| :-------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `invalid_digest`            | Body differs from what was hashed (e.g. pretty-printed vs minified JSON, or body modified after digest computation) | Hash the exact raw body string as sent. Use `JSON.stringify(body)`, not `JSON.stringify(body, null, 2)`. Recompute both digest and signature after any body change. |
| `missing_header_digest`     | Digest header absent on POST/PUT/PATCH                                                                              | Add the `digest` header for requests with a body.                                                                                                                   |
| `missing_header_date`       | Neither `date` nor `x-date` provided                                                                                | Add `date` (or `x-date` in browsers) with RFC 2822 format.                                                                                                          |
| `missing_header_xrequestid` | `x-request-id` header missing                                                                                       | Add `x-request-id` with a UUID v4 value.                                                                                                                            |
| `verification_failed`       | Signing string mismatch or wrong private key                                                                        | Check header order matches the `headers` parameter, and verify the correct private key is used                                                                      |