To generate a message signature, cryptographic keys of the ED25519 variants must first be created.

We currently only support ED25519 key types.

Determine Signature Base

The example signature base below is determined from the content digest example request.

"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: ordered, space ( ) separated and contained within ellipsis (...)
  • meta: unorder, semi-colon (;) separated and specify additional information about the cryptographic operation

The order in which the fields show up in the signature base must match the fields order in the signature params

Generate Message Signature

1

Retrieve Private Key

Identify the private key for the given environment. You should have one for:

  • Staging
  • Production
2

Determine Signature Base

Use the instructions here to determine which fields to include to generate the signature.

3

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).
For the example request below, the computed-signature will be OTEyMjY4...A5NTNDMEQ=
4

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.

5

Make Signed Request

Add Signature and Signature-Input to the request header

  • Signature: sig1=:<computed-signature>:
  • Signature-Input: sig1=<@signature-params>
  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"}'

Verify Message Signature

1

Retrieve Public Key

Identify the public key for the given environment. You should have one for:

  • Staging
  • Production
For webhook message validation, refer to Step 1 in Validate Webhook Message
2

Recompute Message Signature

  1. Use the Signature-Input from the http header to determine the signature base.
  2. Create a sha-512 digest from the signature base

We will call this computed-message-signature.

3

Identify Header Signature

You will need to strip the sig1=: prefix and the : suffix to end up with the header-base64-signature.

In the example below, the header-message-signature equals OTEyMjY4...A5NTNDMEQ=
  curl --request POST \
    --url https://api.pipevest.com/v1/customers \
    --header 'Signature: sig1=:OTEyMjY4...A5NTNDMEQ=:' \
    ....
4

Decode Signature

Using a base64 decoder, decode the header-base64-signature.

We will call this final result, the header-message-signature.

5

Compare Signatures

Compare the computed-message-signature to the header-message-signature.

  computed-message-signature === header-message-signature
6

Verify or Reject

Reject the message, if computed-message-signature does not equal header-message-signature.

Example Verification Code:

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)

Fields

FieldsRequest TypesRequiredNote
Content-TypePOST, PUT, PATCHYes
Content-DigestPOST, PUT, PATCHYes
Content-LengthPOST, PUT, PATCHYes
AuthorizationGET, POST, PUT, PATCH, DELETEYesNot required for /auth
X-Client-IdGET, POST, PUT, PATCH, DELETEYesNot required for /auth
X-Idempotency-KeyPOST, PUT, PATCH, DELETEYes
@methodGET, POST, PUT, PATCH, DELETEYes
@target-uriGET, POST, PUT, PATCH, DELETEYes
@pathGET, POST, PUT, PATCH, DELETEYes
@queryGET, DELETEYes
@signature-paramsGET, POST, PUT, PATCH, DELETEYes

Meta

NameDescriptionRequiredNote
keyidThis is the id of the public key sent over to pipevest.Yesex: staging-pipevest-ed25519
createdThe unix time when the cryptographic operation took placeYes
expiresUnit time stamp that represents created + 100 msNoNot needed, but recommended