NAV Navbar
  • Request Signing in development
  • Request Signing in development

    This guide explains how to communicate with the Form3 API using signed requests. It covers the mechanisms around required setup, signing a request and verifying the signature, followed by a step-by-step guide on how to create a signed message and send it.

    We also provide a debug endpoint to test your signing implementation in our sandbox environment. See this tutorial to learn how to use it.

    Introduction

    Request signing is the process of cryptographically signing messages sent to the Form3 API to increase security and ensure access to the API is restricted to entities that have access to a secret private key.

    The Form3 API uses request signing as defined by the Network Working Group in the Signing HTTP Messages draft using the RSA-SHA256 algorithm.

    Request signing process

    The request signing process consists of two parts: a setup part that has to be completed once and an operational part that is completed every time a request is sent.

    Setup, required once

    1. The client generates a pair of keys:
      • A secret private key to sign the request
      • A public key to verify the request was signed with the private key above
    2. The client keeps the private key and uses it to sign the message
    3. The public key is sent to Form3 using a secure key sharing process

    During operation, required for each request

    1. The client creates a signature using the private key and embeds it into the header of the HTTP request to the Form3 API
    2. Form3 verifies the signature using the public key. The message is accepted if the verification succeeds and discarded otherwise.
    3. All requests and responses are stored in secure storage on the Form3 servers for future audit purposes

    The diagram below illustrates the signature verification process when a signed request is sent to the API.

    Request signing image

    Signed HTTP message format

    A signed request carries the signature in its Authorization header:

    Authorization: Signature keyId="49a7aac7-3cd0-4eca-9697-b195fc2a898a",algorithm="rsa-sha256",
    headers="(request-target) host date accept content-type",
    signature="dGVzdGVzdGVzdHNldHQ="
    


    Parameter Description
    keyId Unique identifier, UUID required The public key ID
    algorithm string required The algorithm used to sign the signature. Has to be rsa-sha256
    headers string required A List of headers used to generate the signature
    signature hash required The hashed signature using base64 encoding

    Requests that contain a body, such as POST and PUT requests, also require a Digest header that is generated by hashing the body content using SHA256.

    Digest: SHA256=XtSlsJ6FNvDG3LemJ/IFxpkmopLKwwSsUGJebLIiZH0=
    


    Here is an example of a signed request header for a POST request:

    POST request with Digest and Signature

    POST /v1/transaction/payments/ HTTP/1.1
    Host: api.form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Accept: application/vnd.api+json
    Content-Type: application/vnd.api+json
    Content-Lenght: 8
    Digest: SHA256=XtSlsJ6FNvDG3LemJ/IFxpkmopLKwwSsUGJebLIiZH0=
    Authorization: Signature keyId="49a7aac7-3cd0-4eca-9697-b195fc2a898a",algorithm="rsa-sha256",
    headers="(request-target) host date accept digest content-lenght content-type",
    signature="dGVzdHNldHNldHNldHNldHNhZGFkYXNkIGkgdGFraWUgdGFt"
    
    { body }
    

    Signed request example

    In this example, we will sign a POST request to create a Payment resource. We'll use the openssl tool to create the keys and the signature. Make sure you have it installed on your machine to follow along.

    The example will take you through the following steps:

    The request we want to sign in this example is a POST request to create a payment resource. The unsigned version of it looks like this:

    Unsigned HTTP POST request

    POST /v1/transaction/payments/ HTTP/1.1
    Host: api.test.form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Accept: application/vnd.api+json
    Content-Type: application/vnd.api+json
    Content-Length: 1137
    
    {
        "data": {
            "type": "payments",
            "id": "1234567890",
            "version": 0,
            "organisation_id": "1234567890",
            "attributes": {
                "amount": "200.00",
                "beneficiary_party": {
                    "account_name": "Mrs Receiving Test",
                    "account_number": "71268996",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "400302",
                        "bank_id_code": "GBDSC"
                    }
                },
                "currency": "GBP",
                "debtor_party": {
                    "account_name": "Mr Sending Test",
                    "account_number": "87654321",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "1234567890",
                        "bank_id_code": "GBDSC"
                    }
                },
                "processing_date": "2019-20-5",
                "reference": "Something",
                "payment_scheme": "FPS",
                "scheme_payment_sub_type": "TelephoneBanking",
                "scheme_payment_type": "ImmediatePayment"
            }
        }
    }
    

    Generating the private key

    The first step is to generate a private key. You can do this using opensll by running: openssl genrsa -out private_key.pem 2048 The command will create a new key and store it in a file called private_key.pem. The content of the file should look similar to this:

    Test RSA private key

    -----BEGIN RSA PRIVATE KEY-----
    MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
    NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
    UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
    AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
    QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
    kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
    f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
    412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
    mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
    kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
    gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
    G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
    7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
    -----END RSA PRIVATE KEY-----
    


    Your private key will naturally be different from the one in this example. To follow along using the same key that we are, you can download our example key here.

    Creating the Digest header

    Since we're signing a POST request, we need to generate a Digest header. Note that you can skip this step when signing a GET request or other requests that don't contain a body.

    The Digest is a hashed value generated from the body of the request. Form3 expects the hash to be generated using SHA256. Use this file as an input for openssl. It contains the body of the request message as shown above.

    The following command creates the digest hash: echo -n "$(<request_signing_test_message_content.txt)" | openssl dgst -sha256 -binary | openssl enc -base64 -A

    Add the output value of the command to the HTTP request 'Digest' header like this:

    Digest Header

    Digest: SHA256=WllU95a/P37KDBmTedpEIIvVtBgRqDdYrHz06NXDuvk=
    


    The echo command is used to load the file here to remove the \n (newline) character from the end of the file.

    You can check if your file ends with \n by running: xxd -p <file>

    If there is 0a at the end of the hex dump, the file ends with a \n character. You can remove this character by running: truncate -s -1 <file>

    Creating the Authorization header

    For signed requests, the Authorization header consists of 4 parts:

    Signature

    To generate the signature, we first need to create a signature string. The signature string for our request looks like this (you can download the signature string as a text file here):

    Signature string

    (request-target): post /v1/transaction/payments
    host: api.test.form3.tech
    date: Fri, 29 Feb 2019 14:00:00 GMT
    content-type: application/vnd.api+json
    accept: application/vnd.api+json
    digest: SHA-256=WllU95a/P37KDBmTedpEIIvVtBgRqDdYrHz06NXDuvk=
    content-length: 646
    


    The signature string is constructed by taking all headers of the request and separating them by \n characters, as well as adding the (request-target) header. It contains both the request's path and query (our example request does not have a query, however). Examples for request target headers for different requests include:

    Note that since the signature string is used to generate the signature, the order of the header elements always needs to be the same. The Form3 API requires the following headers to be present in a signature string, in this order:

    Using the signature string, we can now generate the signature by signing the signature string with our private key and encoding it using base64.

    You can do this using openssl: echo -n "$(<request_signing_test_signature.txt)" | openssl dgst -binary -sign test_private_key.pem | openssl enc -base64 -A

    The command returns:

    gJ5uVc9rH8saJ0q6ZFqesQ/cmGNsTiaVWAnWwC3AsAFo1+cVHoZUF6wVoYfmZ7OJ3bVf9wIRo6v7GXdJ9HJ+c9+PAC7294mNvAFEE80nqh8ZpD+LDxpNnksG+Ra2B7Lm9WjFecjDVKyKriED0l8AWUr3KrPZY8ueZyptwqSAN5s=
    

    Final Authorization header

    With the signature calculated we are ready to create our Authorization header:

    Test Authorization Header

    Authorization: Signature keyId="75a8ba12-fff2-4a52-ad8a-e8b34c5ccec8",algorithm="rsa-sha256",headers="(request-target) host date content-type accept digest content-length",signature="gJ5uVc9rH8saJ0q6ZFqesQ/cmGNsTiaVWAnWwC3AsAFo1+cVHoZUF6wVoYfmZ7OJ3bVf9wIRo6v7GXdJ9HJ+c9+PAC7294mNvAFEE80nqh8ZpD+LDxpNnksG+Ra2B7Lm9WjFecjDVKyKriED0l8AWUr3KrPZY8ueZyptwqSAN5s="
    


    Note that the order of the headers has to match their order in the signature string.

    Sending the signed request

    Finally, send the signed request using the POST /v1/transaction/payments API call. The completed request looks like this:

    Signed request example

    POST /v1/transaction/payments/ HTTP/1.1
    Host: api.test.form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    Digest: SHA-256=EgxQykHchUP4ywDh9J3VX/LYaQreIBzC+1Q53lcJhBI=
    Content-Length: 646
    Authorization: Signature keyId="75a8ba12-fff2-4a52-ad8a-e8b34c5ccec8",algorithm="rsa-sha256",headers="(request-target) host date content-type accept digest content-length",signature="gJ5uVc9rH8saJ0q6ZFqesQ/cmGNsTiaVWAnWwC3AsAFo1+cVHoZUF6wVoYfmZ7OJ3bVf9wIRo6v7GXdJ9HJ+c9+PAC7294mNvAFEE80nqh8ZpD+LDxpNnksG+Ra2B7Lm9WjFecjDVKyKriED0l8AWUr3KrPZY8ueZyptwqSAN5s="
    
    {
        "data": {
            "type": "payments",
            "id": "1234567890",
            "version": 0,
            "organisation_id": "1234567890",
            "attributes": {
                "amount": "200.00",
                "beneficiary_party": {
                    "account_name": "Mrs Receiving Test",
                    "account_number": "71268996",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "400302",
                        "bank_id_code": "GBDSC"
                    }
                },
                "currency": "GBP",
                "debtor_party": {
                    "account_name": "Mr Sending Test",
                    "account_number": "87654321",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "1234567890",
                        "bank_id_code": "GBDSC"
                    }
                },
                "processing_date": "2019-20-5",
                "reference": "Something",
                "payment_scheme": "FPS",
                "scheme_payment_sub_type": "TelephoneBanking",
                "scheme_payment_type": "ImmediatePayment"
            }
        }
    }