# API Request Signing

Source: https://docs.gocardless.com/docs/send-money/api-request-signing

# API request signing

We require API Request Signing to secure your communication to the GoCardless API and to comply with the Strong Customer Authentication requirement. This section will walk you through how to:

- Generate public and private keys and upload the public key to GoCardless
- Build the signature base that will be used to generate the Gc-Signature header
- Build and attach the Gc-Signature and Gc-Signature-Input headers and send them with your API request

## Sandbox developer bypass

For ease of development (e.g. testing our endpoints with Postman or curl), it is possible to bypass request signing in the sandbox environment using the following header:

```http
Gc-Signature: sig-1=:DEVELOPER:
```

## Generate your key pair

1. We use ECDSA keys for API request signing. You must generate an ES512 key pair using the P-521 elliptic curve without a passphrase. Use the following commands in a terminal to generate your key pair:

```bash
openssl ecparam -name secp521r1 -genkey -noout -out ec-secp521r1-priv-key.pem
openssl ec -in ec-secp521r1-priv-key.pem -pubout > ec-secp521r1-pub-key.pem
```

2. Securely save your private key.
3. Upload your public key to GoCardless via the dashboard.
   - Go to [Settings → Account settings](https://manage-sandbox.gocardless.com/company/settings).
   - Under "API request signing", add a new public key.

```bash
cat ec-secp521r1-pub-key.pem
# Copy this output and paste into the form. It should look something like:
# -----BEGIN PUBLIC KEY-----
# MIGbMBAGByqG.....................
# .................................
# ................
# -----END PUBLIC KEY-----
```

When you've added a new public key, a corresponding request_signing_key ID (e.g. `RSK00123456789300123456789300`) is generated. You will need this for building the request signature.

## Signing the API request

We follow the [RFC-9421](https://datatracker.ietf.org/doc/rfc9421/) specification for HTTP Message Signatures.

1. Create the signature base. This is a raw string containing all the components covered by the signature. It must be in the format shown in the example below.

```signature
"@method": POST
"@authority": api.gocardless.com
"@request-target": /path?param=value
"content-digest": sha256=:9AmU2hRsZnqEo2HiNTLacLN0fOs8YmDiuX4WYeYWvh0=:
"content-type": application/json
"content-length": 18
"@signature-params": ("@method" "@authority" "@request-target" "content-digest" "content-type" "content-length");keyid="your-public-key-identifier";created=1675688690;nonce="8IBTHwOdqNKAWeKl7plt8g=="
```

```ruby
signature_base =
  "\\"@method\\": POST\\n" \\
  "\\"@authority\\": api.gocardless.com\\n" \\
  "\\"@request-target\\": /path?param=value\\n" \\
  "\\"content-digest\\": sha256=:9AmU2hRsZnqEo2HiNTLacLN0fOs8YmDiuX4WYeYWvh0=:\\n" \\
  "\\"content-type\\": application/json\\n" \\
  "\\"content-length\\": 18\\n" \\
  "\\"@signature-params\\": (\\"@method\\" \\"@authority\\" \\"@request-target\\" \\"content-digest\\" \\"content-type\\" \\"content-length\\");keyid=\\"your-public-key-identifier\\";created=1675688690;nonce=\\"8IBTHwOdqNKAWeKl7plt8g==\\""
```

```c#
const signatureBase = SignatureBase(
    httpMethod: "POST",
    host: "https://api.gocardless.com",
    requestPath: "/outbound_payments",
    contentDigest: "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    contentType: "application/json",
    contentLength: 123,
    keyId: "RSK00123456789300123456789300",
    created: DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),
    nonce: Guid.NewGuid().ToString());

string SignatureParams(string keyId, string created, string nonce, bool includeContentParams)
=> @\$"(""@method"" ""@authority"" ""@request-target""" +
(includeContentParams ? @" ""content-digest"" ""content-type"" ""content-length""" : "") +
@\$");keyid=""\"";created=\;nonce=""\""";

string SignatureBase(
string httpMethod, string host, string requestPath,
string? contentDigest, string? contentType, int? contentLength,
string keyId, string created, string nonce
)
=> (\$"\\"@method\\": \\\n" +
\$"\\"@authority\\": \\\n" +
\$"\\"@request-target\\": \\\n" +
(string.IsNullOrWhiteSpace(contentDigest) ? "" :
\$"\\"content-digest\\": sha256=:#\:\\n" +
\$"\\"content-type\\": \\\n" +
\$"\\"content-length\\": \\\n") +
\$"\\"@signature-params\\": \");
```

```nodejs
// Example for POST request
const signatureBase = \`"@method": POST\\n\` +
\`"@authority": api.gocardless.com\\n\` +
\`"@request-target": /path?param=value\\n\` +
\`"content-digest": sha256=:9AmU2hRsZnqEo2HiNTLacLN0fOs8YmDiuX4WYeYWvh0=:\\n\` +
\`"content-type": application/json\\n\` +
\`"content-length": 18\\n\` +
\`"@signature-params": \` +
\`("@method" "@authority" "@request-target"\` +
\` "content-digest" "content-type" "content-length"\` +
\`);keyid="RSK00123456789300123456789300";created=1675688690;nonce="8IBTHwOdqNKAWeKl7plt8g=="\`;

// Example for GET request
const signatureBaseGet = \`"@method": GET\\n\` +
\`"@authority": api.gocardless.com\\n\` +
\`"@request-target": /path?param=value\\n\` +
\`"@signature-params": \` +
\`("@method" "@authority" "@request-target"\` +
\`);keyid="RSK00123456789300123456789300";created=1675688690;nonce="8IBTHwOdqNKAWeKl7plt8g=="\`;
```

**Requirements:**

1. Each line is concatenated with a **newline character (`\n`)**.
2. You must **respect the format** (e.g., respect every character like `"`, `@`, or spaces).
3. `@method`, `@authority`, and `@request-target` are **required components** in building the signature base.

You can add any other signed headers you want to include into the signature base. They **must appear in the same order** as listed in `@signature-params`. For any additional signed headers, you **must pass them as HTTP headers** in the request itself.

The following table describes how the signature base is built:

| Key                   | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `"@method"`           | The HTTP method in uppercase, e.g. `GET` or `POST`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `"@authority"`        | The target URI host, usually conveyed via the Host header (e.g. `api.gocardless.com`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `"@request-target"`   | The full path including any query parameters. Query parameters must be ordered deterministically by sorting keys.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `"content-digest"`    | _Only needed if the request has a body (e.g. POST/PUT)._ SHA-256 base64-encoded hash of the body, wrapped as `sha256=:CONTENT_DIGEST:`. The body must be serialised deterministically (sorted keys) to produce a consistent digest.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `"content-type"`      | _Only needed if the request has a body._ The content type, e.g. `application/json`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `"content-length"`    | _Only needed if the request has a body._ Size of the message body in bytes.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `"@signature-params"` | The following signature parameters should be passed as follows and separated by semicolon: `();;` **Signature Components** In brackets — the components of the request that are included in the signature process. This would be `"@method" "@authority" "@request-target"`, followed by any additional request headers, each separated by a space. **They should appear in the order as you used to build the rest of the signature base.** **Signature Parameters** `keyid` — the identifier of your public request signing key. `created` — a Unix timestamp (e.g. 1675688690) in UTC. `nonce` — a [random unique value](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) generated for this signature as a String value. This must be a random base64-encoded string of at least 128 bits of data from a cryptographically secure random number generator. |

More details on building the signature base can be found in section 2.5 of [RFC-9421](https://datatracker.ietf.org/doc/rfc9421/).

2. Sign the signature base with your private key.

```ruby
private_key = OpenSSL::PKey::EC.new(your_private_key)
signature = private_key.sign(OpenSSL::Digest.new("SHA512"), signature_base)
```

```c#
using var privateKeyPem = File.OpenText("ec-secp521r1-priv-key.pem");
var signature = GetSignature(privateKeyPem, signatureBase);

string GetSignature(TextReader privateKeyPem, string message)
\
```

```javascript
import \* as crypto from "node:crypto";

// Read in signature file
const privateKeyPem = fs.readFileSync("ec-secp521r1-priv-key.pem", "utf-8");

// Create a private key object
const privateKey = crypto.createPrivateKey(\);

// Prepare the data for signing
const dataBuffer = Buffer.from(signatureBase, "utf8");

// Generate the signature
const sign = crypto.createSign("SHA512");
sign.update(dataBuffer);
sign.end();
const signature = sign.sign(privateKey, "base64");

console.log("Signature:", signature);
```

3. Encode the signature in base64.

```ruby
encoded_signature = Base64.strict_encode64(signature)
```

```c#
const signatureStr = Convert.ToBase64String(signature);
```

4. Set the `Gc-Signature` and `Gc-Signature-Input` HTTP headers.

**Requirements:**

- `Gc-Signature` is the base64 encoded signature. It must be in the format `Gc-Signature: sig-1=::` where `sig-1` identifies the signature and the value is wrapped in colons.
- `Gc-Signature-Input` is the same string as `@signature-params` used in building the signature base. It must be in the format `sig-1=`.

```http
Gc-Signature-Input: sig-1=("@method" "@authority" "@request-target" "content-digest" "content-type" "content-length");keyid="your-public-key-identifier";created=1675688690;nonce="8IBTHwOdqNKAWeKl7plt8g=="

Gc-Signature: sig-1=:LjbtqUbfmvjj5C5kr1Ugj4PmLYvx9wVjZvD9GsTT4F7GrcQ\
     EdJzgI9qHxICagShLRiLMlAJjtq6N4CDfKtjvuJyE5qH7KT8UCMkSowOB4+ECxCmT\
     8rtAmj/0PIXxi0A0nxKyB09RNrCQibbUjsLS/2YyFYXEu4TRJQzRw1rLEuEfY17SA\
     RYhpTlaqwZVtR8NV7+4UKkjqpcAoFqWFQh62s7Cl+H2fjBSpqfZUJcsIk4N6wiKYd\
     4je2U/lankenQ99PZfB4jY3I5rSV2DSBVkSFsURIjYErOs0tFTQosMTAoxk//0RoK\
     UqiYY8Bh0aaUEb0rQl3/XaVe4bXTugEjHSw==:
```

5. Send your request with the attached headers.

If making a request with a payload, the payload sent must have the same structure as used to generate the `content-digest` to ensure they match during verification.

## Code Example

Example methods for building the signature headers:

```ruby
module SignatureHelper

### Build Signature Base

def self.signature_base(http_method, host, request_uri, content_digest, content_type, content_length, key_id, created, nonce)
signature_base = <<~SIGBASE
"@method": #\
"@authority": #\
"@request-target": #\
"content-digest": sha256=:#\:
"content-type": #\
"content-length": #\
"@signature-params": #\
SIGBASE

    signature_base.strip

end

### Build Signature Params

def self.signature_params(key_id, created, nonce)
'("@method" "@authority" "@request-target" "content-digest" "content-type" "content-length");keyid="%s";created=%d;nonce="%s"' % [key_id, created, nonce]
end

### Sign and return base64 encoded signature for \`Gc-Signature\` header

def self.signature_header(private_key_pem, signature_base)
private_key = OpenSSL::PKey::EC.new(private_key_pem)
signature = private_key.sign(OpenSSL::Digest.new("SHA512"), signature_base)
signature = Base64.strict_encode64(signature)

    "sig-1=:#\:"

end

### Return value for \`Gc-Signature-Input\` header

def self.signature_input_header(key_id, created, nonce)
"sig-1=#\"
end

### Sort query parameters to be used in request URL and @request-target

def self.order_query_params(original_url)
return original_url if original_url.nil? || original_url.empty?

    uri = URI.parse(original_url)
    return original_url unless uri.query

    query_params = URI.decode_www_form(uri.query).sort
    uri.query = URI.encode_www_form(query_params)
    uri.to_s

end

### Sort request body to be used in request and Content-Digest

def self.order_request_body(request_body)
json_string = JSON.generate(sort_hash(request_body))
json_string
end

def self.sort_hash(hash) # Recursively sort the hash and its nested hashes
hash.each_with_object(\) do |(key, value), sorted_hash|
sorted_key = key.to_s # Convert key to string
sorted_value = value.is_a?(Hash) ? sort_hash(value) : value
sorted_hash[sorted_key] = sorted_value
end.sort.to_h # Sort the hash by keys and convert back to a hash
end

### Calculate Content-Digest

def self.content_digest(request_body)
digest = OpenSSL::Digest.new("SHA256").digest(request_body)
Base64.strict_encode64(digest)
end
end
```

```c#
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

class SignatureHelper
\

    public string GcSignature \
    public string GcSignatureInput \

    private void verifySignature(TextReader publicKeyPem, string signature, string signatureBase)
    \
    \}

    public static string HashWithSHA256(string value)
    \

    private string SignatureHeader(string signature)
    \:";
    \}

    private string SignatureInputHeader(string keyId, string created, string nonce, bool includeContentParams)
        => $"sig-1=\";

    private string SignatureBase(
        string httpMethod, string host, string requestPath,
        string? contentDigest, string? contentType, int? contentLength,
        string keyId, string created, string nonce)
        => ($"\\"@method\\": \\\n" +
            $"\\"@authority\\": \\\n" +
            $"\\"@request-target\\": \\\n" +
            (string.IsNullOrWhiteSpace(contentDigest) ? "" :
                $"\\"content-digest\\": sha256=:#\:\\n" +
                $"\\"content-type\\": \\\n" +
                $"\\"content-length\\": \\\n") +
            $"\\"@signature-params\\": \")
            .Trim();

    private string SignatureParams(string keyId, string created, string nonce, bool includeContentParams)
    \"";created=\;nonce=""\""";
    \}

    private string GetSignature(TextReader privateKeyPem, string message)
    \

    private byte[] GetMessageBytes(string message) => Encoding.UTF8.GetBytes(message);

\}
```

```javascript
import \ from "gocardless-nodejs/client"

class SignatureHelper \

    private \_gcSignature: string;
    private \_gcSignatureInput: string;

    public getGcSignature(): string \
    public getGcSignatureInput(): string \

    public static getRequestBody(client: GoCardlessClient, method: 'put' | 'post', requestParameters: any, payloadKey: string): string | undefined \

    public static getSha256DigestHeader(digest: string): string \:\`;
    \}

    public static getSha256Digest(
        content: string,
        algorithm: string = 'sha256',
        encoding: crypto.BinaryToTextEncoding = 'base64'
    ): string \

            const hash = crypto.createHash(algorithm);
            hash.update(content, 'utf8');
            const digest = hash.digest(encoding);

            return digest;
        \} catch (error) \:\`, error);
            throw error;
        \}
    \}

    private getSignatureBase(httpMethod: string, host: string, requestPath: string, keyId: string, created: string, nonce: string, contentDigest?: string, contentType?: string, contentLength?: number): string \\\n\` +
            \`\\"@authority\\": \$\\\n\` +
            \`\\"@request-target\\": \$\\\n\` +
            (!contentDigest ? "" :
                \`\\"content-digest\\": sha256=:\$\:\\n\` +
                \`\\"content-type\\": \$\\\n\` +
                \`\\"content-length\\": \$\\\n\`) +
            \`\\"@signature-params\\": \$\\`)
            .trim();
    \}

    private getSignatureHeader(signature: string): string \:\`;
    \}

    private getSignatureInputHeader(keyId: string, created: string, nonce: string, includeContentParams: boolean): string \\`;
    \}

    private getSignatureParams(keyId: string, created: string, nonce: string, includeContentParams: boolean): string \";created=\$\;nonce="\$\"\`;
    \}

    private getSignature(privateKeyPem: string, message: string, publicKeyPem?: string): string \
        \}
        return sig;
    \}

    private signStringSecp521r1(privateKeyPem: string, dataToSign: string): string \);

            const dataBuffer = Buffer.from(dataToSign, 'utf8');
            const sign = crypto.createSign('SHA512');
            sign.update(dataBuffer);
            sign.end();
            const signature = sign.sign(privateKey, 'base64');

            return signature;
        \} catch (error) \
    \}

    private verifySignatureSecp521r1(publicKeyPem: string, dataToVerify: string, signatureBase64: string): boolean \);

            const dataBuffer = Buffer.from(dataToVerify, 'utf8');
            const verify = crypto.createVerify('SHA512');
            verify.update(dataBuffer);
            verify.end();
            const isValid = verify.verify(publicKey, signatureBase64, 'base64');

            return isValid;
        \} catch (error) \
    \}

\}
```

You can then use this code to generate the HTTP Signature headers and attach them to your request:

```ruby
def make_request(request_body, request_uri, private_key)
  sorted_request_body = SignatureHelper.order_request_body(request_body)
  content_digest = SignatureHelper.content_digest(sorted_request_body)
  content_length = sorted_request_body.bytesize
  content_type = "application/json"

sorted_request_uri = SignatureHelper.order_query_params(request_uri)

created = Time.now.to_i
nonce = SecureRandom.alphanumeric(16)
key_id = "YOUR_REQUEST_SIGNING_KEY_ID"

signature_base = SignatureHelper.signature_base("POST", "api-sandbox.gocardless.com", sorted_request_uri, content_digest, content_type, content_length, key_id, created, nonce)

signature_header = SignatureHelper.signature_header(private_key, signature_base)
signature_input_header = SignatureHelper.signature_input_header(key_id, created, nonce)

# Make the request attaching the required headers

end
```

```c#
using GoCardless;
using GoCardless.Services;

// Init the GoCardless .NET client
var accessToken = "live_XXXXXXXXXXXXXX"; // your API access token
var gocardless = GoCardlessClient.Create(accessToken, GoCardlessClient.Environment.SANDBOX);

// Set up request signing
// Provide the actual path to your file, or load the pem text into a TextReader from memory
using var privateKeyPem = File.OpenText("ec-secp521r1-priv-key.pem");

// You can get the public key from the dashboard once you upload the public certificate
string keyId = "RSK0000000000000000000";

// This request action will sign every request made by the client, and attach the right headers.
var signApiRequest = new Action(request =>
\

\});
var requestSettings = new GoCardless.Internals.RequestSettings \;

// Get an outbound payment by ID
var outboundPayment = await gocardless.OutboundPayments.GetAsync("OUTXXXXXXXXXXXXXXX", null, requestSettings);
```

```javascript
import \ from "gocardless-nodejs/constants";

async function main() \);
const privateKeyPem = fs.readFileSync("ec-secp521r1-priv-key.pem", 'utf-8')
// @ts-ignore
const baseUrl = client.\_api.\_baseUrl;
const host = new URL(baseUrl).host;
const keyId = "";

    // Example POST request
    const postRequestPath = \`/outbound_payments\`;
    const requestParams = \
    \};
    const requestBody = SignatureHelper.getRequestBody(client, "post", requestParams, 'outbound_payments')!;
    const digest = SignatureHelper.getSha256Digest(requestBody);
    const contentLength = Buffer.byteLength(requestBody, 'utf8');
    const sigHelperPost = new SignatureHelper(
        privateKeyPem,
        "POST",
        host,
        postRequestPath,
        keyId,
        digest,
        contentLength,
    );
    const obp = await client.outboundPayments.create(requestParams, uuid(), \);
    console.log(obp);

    // Example GET request
    const id = obp.id
    const getRequestPath = \`/outbound_payments/\$\\`;
    const sigHelperGet = new SignatureHelper(
        privateKeyPem,
        "GET",
        host,
        getRequestPath,
        keyId,
    )
    const updated_obp = await client.outboundPayments.find(id, \)
    console.log(updated_obp);

\}

main().catch(error => \);
```

## API Response

API requests that are correctly signed will be processed normally. If the signature is invalid, the API will return **401 Unauthorized**.

## Test your request signing

To test whether you are correctly building request signatures, make a POST request to the `/test-signature` endpoint. It will validate the `Gc-Signature` and `Gc-Signature-Input` headers and return **204 No Content** if valid. No further validation is performed on any request body passed.

```bash
curl --silent \
-H "Content-Type: application/json" \
-H "Content-Digest: sha256=:dg0ak4ae6PgXhyxkn0FYx0th5QxzaDabkM2wBtufB2g=:" \
-H "Content-Length: 16" \
-H "Gocardless-Version: 2015-07-06" \
-H "Authorization: Bearer $" \
-H "Gc-Signature: sig-1=:$:" \
-H "Gc-Signature-Input: sig-1=(\"@method\" \"@authority\" \"@request-target\" \"content-digest\" \"content-type\" \"content-length\");keyid=\"$\";created=$;nonce=\"$\"" \
"https://api.gocardless.com/test_signature" \
-d @- << EOF

EOF
```

## What's next?

  
#### [Send an Outbound Payment](/docs/send-money/send-an-outbound-payment)

Add a recipient and initiate your first outbound payment using signed requests.

  
#### [Strong Customer Authentication](/docs/send-money/strong-customer-authentication)

Learn how SCA works alongside request signing for outbound payments.