Skip to content

Validations

Partly live. Public-key retrieval — GET /v1/webhooks/key — is live today, and the signature scheme on this page is exactly what Pipevest signs with. Webhook subscriptions and outbound delivery are still on our roadmap, so no webhooks are being sent yet; until they ship, learn a payment outcome by polling payment status. If webhooks are part of your integration plan, contact us.

Webhook signatures need to be verified in order to guarantee that the webhook was sent from our system and was not tampered with via a man in the middle attack.

Pipevest signs outbound webhooks with one server-wide ED25519 key per environment — the same key for every partner and every webhook. There is no per-webhook key: configuring a webhook does not generate one, and you never register a key for us to sign with.

The key is identified by the keyid in the Signature-Input header, which is the same value as the keyId and the JWKS kid returned by GET /v1/webhooks/key. Fetch the matching public key once, cache it against the environment’s base url and that key id, and use it to verify every webhook we send.

A key id names exactly one key. It carries both the environment and the generation of that environment’s signing key:

Environment Base URL Key id
Production https://api.pipevest.com pipevest-live-ed25519-2
Sandbox (UAT) https://api.staging.pipevest.com pipevest-staging-ed25519-2

The trailing integer is the key generation, and it increments whenever that environment’s signing key is replaced. Two properties follow, and your verifier can rely on both:

  • A key id never names two different keys. Production and staging never share one, and a generation is never reused.
  • A rotation always changes the key id. The new key arrives under a key id you have not seen.

So a verifier that caches by (base url, key id) meets an unknown key id, not a signature that fails to verify. Treat an unknown key id as the cue to re-fetch GET /v1/webhooks/key from that same base url, cache the key it returns under the key id it reports, and verify with it. Read key ids from the header and from that endpoint rather than hardcoding them.

Retrieve Public Key

Make a GET request to /v1/webhooks/key. Public keys are non-secret, so this endpoint is unauthenticated — no bearer token, no X-Client-Id, and no request signature of your own.

Terminal window
curl --request GET \
--url https://api.pipevest.com/v1/webhooks/key

Returns HTTP 200 with the key in both SPKI PEM and JWKS form — use whichever your verification library prefers.

{
"data": {
"keyId": "pipevest-live-ed25519-2",
"algorithm": "ed25519",
"publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA...\n-----END PUBLIC KEY-----\n",
"jwks": {
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"kid": "pipevest-live-ed25519-2",
"use": "sig",
"alg": "EdDSA"
}
]
}
}
}

Both production and staging serve a key today, so treat a 503 from this endpoint as an incident to report and retry, not as a state to design around.

Every webhook is a POST with a JSON body, signed under the label sig1. The covered components are fixed — they do not vary by event, subscription or destination:

Component Value
"content-type" Always application/json
"content-digest" sha-512=:<base64>: over the exact bytes of the webhook body
"content-length" Byte length of the body
"@method" Always POST
"@target-uri" The full webhook url you registered
"@path" The path component of that url

The signature parameters carry keyid and created only. Pipevest does not send expires on outbound webhooks, so do not reject a webhook for a missing or expired expires — bound replay with created and the payload’s eventId instead.

Content-Type: application/json
Content-Length: 118
Content-Digest: sha-512=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:
Signature: sig1=:OTEyMjY4...A5NTNDMEQ=:
Signature-Input: sig1=("content-type" "content-digest" "content-length" "@method" "@target-uri" "@path");keyid="pipevest-live-ed25519-2";created=1732893484

Rebuilt from the Signature-Input above, for a webhook delivered to https://companyx.com/webhooks:

"content-type": application/json
"content-digest": sha-512=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:
"content-length": 118
"@method": POST
"@target-uri": https://companyx.com/webhooks
"@path": /webhooks
"@signature-params": ("content-type" "content-digest" "content-length" "@method" "@target-uri" "@path");keyid="pipevest-live-ed25519-2";created=1732893484