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

# Message Signature

To generate a [message signature](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-04.html), [cryptographic keys](/concepts/cryptographic-keys) of the ED25519 variants must first be created.

<Warning>
  We currently only support
  [ED25519](https://datatracker.ietf.org/doc/html/rfc8032) key types.
</Warning>

#### Determine Signature Base

The example signature base below is determined from the [content digest example request](/concepts/http-signature/content-digest#generate-content-digest).

```
"Authorization": Bearer 123
"Content-Digest": sha-512=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:
"Content-Length": 18
"Content-Type": application/json
"X-Client-Id": 123456
"X-Idempotency-Key": 123456
"@method": POST
"@target-uri": https://api.pipevest.com
"@path": /v1/customers
"@query: ?sort=ASC
"@signature-params": ("Authorization" "Content-Digest" "Content-Length" "Content-Type" "X-Client-Id" "X-Idempotency-Key" "@method" "@target-uri" "@path" "@query");keyid="staging-pipevest-ed25519";created=1732893484;expires=1732893584
```

#### Signature Parameters

The field `@signature-params` contains an ordered list of components that make up the signature base. It is made up of two sections, fields and meta data.

`"@signature-params": (fields...);meta`

* [**fields**](#fields): ordered, space (` `) separated and contained within ellipsis `(...)`
* [**meta**](#meta): unorder, semi-colon (`;`) separated and specify additional information about the cryptographic operation

<Warning>
  The order in which the fields show up in the signature base **must** match the
  fields order in the signature params
</Warning>

#### Generate Message Signature

<img className="block dark:hidden" height="200" src="https://mintcdn.com/pipevest/l6kPGYU9W55pgekc/images/http-message-signature.png?fit=max&auto=format&n=l6kPGYU9W55pgekc&q=85&s=9fa939dc29c62ba4416a3c43527d6c7e" data-path="images/http-message-signature.png" />

<img className="hidden dark:block" height="200" src="https://mintcdn.com/pipevest/l6kPGYU9W55pgekc/images/http-message-signature-dark.png?fit=max&auto=format&n=l6kPGYU9W55pgekc&q=85&s=b0e0a71748a4e271c48d5eb8085705e1" data-path="images/http-message-signature-dark.png" />

<Steps>
  <Step title="Retrieve Private Key">
    Identify the private key for the given environment. You should have one for:

    * **Staging**
    * **Production**

    <Tip>How to [Generate Cryptographic keys](/concepts/cryptographic-keys#instructions-to-generate-keys)</Tip>
  </Step>

  <Step title="Determine Signature Base">
    Use the [instructions here](#determine-signature-base) to determine which fields to include to generate the signature.
  </Step>

  <Step title="Calculate Signature">
    1. Using sha-512, hash the signature base
    2. Sign the resulting hash with the environment specific private key.
    3. Base64 encode the signed hash, this is the final computed message signature (`computed-signature`).

    <Note>For the example request below, the `computed-signature` will be `OTEyMjY4...A5NTNDMEQ=`</Note>
  </Step>

  <Step title="Signature Input">
    The signature input is the same as the `@signature-params` and will be passed along the header. The inputs allow for the signature to be recomputed and verified.
  </Step>

  <Step title="Make Signed Request">
    Add `Signature` and `Signature-Input` to the request header

    * Signature: `sig1=:<computed-signature>:`
    * Signature-Input: `sig1=<@signature-params>`

    ```bash theme={null}
      curl --request POST \
        --url https://api.pipevest.com/v1/customers?sort=ASC \
        --header 'Content-Digest: sha-512=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:' \
        --header 'Content-Length: 18' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer 123456' \
        --header 'Signature: sig1=:OTEyMjY4...A5NTNDMEQ=:' \
        --header 'Signature-Input: sig1=("Content-Type" "Content-Digest" "Content-Length" "Authorization" "X-Client-Id" "X-Idempotency-Key" "@method" "@target-uri" "@path" "@query");keyid="staging-pipevest-ed25519";created=1732893484;expires=1732893584' \
        --header 'X-Client-Id: 123456' \
        --header 'X-Idempotency-Key: 123456' \
        ...
        --data '{"firstName": "John", "lastName": "Doe"}'
    ```
  </Step>
</Steps>

#### Verify Message Signature

<img className="block dark:hidden" height="200" src="https://mintcdn.com/pipevest/l6kPGYU9W55pgekc/images/verify-message-signature.png?fit=max&auto=format&n=l6kPGYU9W55pgekc&q=85&s=ea6da094883ee697d164c8e2ff172fd9" data-path="images/verify-message-signature.png" />

<img className="hidden dark:block" height="200" src="https://mintcdn.com/pipevest/l6kPGYU9W55pgekc/images/verify-message-signature-dark.png?fit=max&auto=format&n=l6kPGYU9W55pgekc&q=85&s=a983815443ff830e19446dad4433b66f" data-path="images/verify-message-signature-dark.png" />

<Steps>
  <Step title="Retrieve Public Key">
    Identify the public key for the given environment. You should have one for:

    * **Staging**
    * **Production**

    <Tip>For webhook message validation, refer to `Step 1` in [Validate Webhook Message](/concepts/webhook/validation#validate-webhook-message)</Tip>
  </Step>

  <Step title="Recompute Message Signature">
    1. Use the `Signature-Input` from the http header to [determine the signature base](#determine-signature-base).
    2. Create a `sha-512` digest from the signature base

    <Note>
      We will call this `computed-message-signature`.
    </Note>
  </Step>

  <Step title="Identify Header Signature">
    You will need to strip the `sig1=:` prefix and the `:` suffix to end up with the `header-base64-signature`.

    <Tip>In the example below, the `header-message-signature` equals `OTEyMjY4...A5NTNDMEQ=`</Tip>

    ```bash theme={null}
      curl --request POST \
        --url https://api.pipevest.com/v1/customers \
        --header 'Signature: sig1=:OTEyMjY4...A5NTNDMEQ=:' \
        ....
    ```
  </Step>

  <Step title="Decode Signature">
    Using a base64 decoder, decode the `header-base64-signature`.

    <Note>
      We will call this final result, the `header-message-signature`.
    </Note>
  </Step>

  <Step title="Compare Signatures">
    Compare the `computed-message-signature` to the `header-message-signature`.

    ```
      computed-message-signature === header-message-signature
    ```
  </Step>

  <Step title="Verify or Reject">
    Reject the message, if `computed-message-signature` does not equal `header-message-signature`.
  </Step>
</Steps>

#### Example Verification Code:

<Tabs>
  <Tab title="Javascript">
    ```javascript theme={null}
    import crypto from 'node:crypto'

    // Get the Public key
    const publicKey = ...

    // Get Signature and Signature inputs from the original http message header
    const signature = request.headers['Signature']
    const signatureInput = request.headers['Signature-Input']

    // Recompute message signature using the signature inputs
    const computedSignatureBase = `...` // determined using `signatureInput`
    const computedMessageSignature = crypto.createHash('sha512').update(computedSignatureBase).digest()

    // Decode the signature to get the signed http message
    const headerBase64Signature = signature.match(/:(.*):/).pop()
    const headerMessageSignature = Buffer.from(headerBase64Signature, 'base64')

    // Use public key to verify that computed and header signatures are the same
    const isSignatureVerified = crypto.verify(null, computedMessageSignature, publicKey, headerMessageSignature)
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
      import hashlib
      import re

      # Get the Public key
      public_key = ...

      # Get Signature and Signature inputs from the original http message header
      signature = request.headers.get("Signature", "")
      signature_input = request.headers.get("Signature-Input", "")

      # Recompute message signature using the signature inputs
      computed_signature_base = `...` # determined using `signature_input`
      computed_message_signature = hashlib.sha512().update(computed_signature_base)

      # Decode the header signature to get the signed http message
      header_base64_signature = re.search(r'\:(.*)\:', signature).group(1)
      header_message_signature = base64.b64decode(header_base64_signature)

      # Use public key to verify that computed and header signatures are the same
      is_signature_verified = ... # use `public_key` to make this determination
    ```
  </Tab>
</Tabs>

#### Fields

| Fields              |              Request Types              | Required |           Note           |
| :------------------ | :-------------------------------------: | -------: | :----------------------: |
| `Content-Type`      |          `POST`, `PUT`, `PATCH`         |      Yes |                          |
| `Content-Digest`    |          `POST`, `PUT`, `PATCH`         |      Yes |                          |
| `Content-Length`    |          `POST`, `PUT`, `PATCH`         |      Yes |                          |
| `Authorization`     | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |      Yes | Not required for `/auth` |
| `X-Client-Id`       | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |      Yes | Not required for `/auth` |
| `X-Idempotency-Key` |     `POST`, `PUT`, `PATCH`, `DELETE`    |      Yes |                          |
| `@method`           | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |      Yes |                          |
| `@target-uri`       | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |      Yes |                          |
| `@path`             | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |      Yes |                          |
| `@query`            |             `GET`, `DELETE`             |      Yes |                          |
| `@signature-params` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |      Yes |                          |

#### Meta

| Name      |                        Description                        | Required |              Note              |
| :-------- | :-------------------------------------------------------: | -------: | :----------------------------: |
| `keyid`   |  This is the id of the public key sent over to pipevest.  |      Yes | ex: `staging-pipevest-ed25519` |
| `created` | The unix time when the cryptographic operation took place |      Yes |                                |
| `expires` |     Unit time stamp that represents `created + 100 ms`    |       No |   Not needed, but recommended  |
