This is a companion to Signature verification for integrators debugging verification failures. When your library-based verification keeps returningDocumentation Index
Fetch the complete documentation index at: https://docs.flatpeak.com/llms.txt
Use this file to discover all available pages before exploring further.
false and you can’t tell why, reproducing the check on the command line with OpenSSL isolates the problem to a single step — bad bytes, wrong key, or wrong algorithm settings.
What you need before starting
| Item | Where to get it |
|---|---|
The exact Flatpeak-Timestamp value | Logged from the incoming request, or Dashboard → Webhooks → Logs. |
The exact Flatpeak-Signature value (with v1= prefix) | Same — copy verbatim, do not let your shell trim or re-encode it. |
| The raw request body bytes | Captured before any JSON parsing. Saving from a log or framework debugger is risky — see the warning below. |
| The signing public key (PEM) | From the JWKS at https://api.flatpeak.com/jwks.json. Match the key whose kid equals the Flatpeak-Key-ID header. |
If your application logs the body via a JSON pretty-printer or a framework
that re-serialised the payload, the bytes you captured are not what was
signed. Always work from the raw request bytes.
How webhook signing works
Flatpeak signs the byte string{Flatpeak-Timestamp}.{raw_request_body} using RSA-PSS with SHA-256 (PS256), MGF1-SHA256, salt length = 32 (digest length), with an RSA-2048 key. The signature is base64url-encoded (no padding) and prefixed with the scheme: v1=<base64url>.
The full header set:
| Header | Description |
|---|---|
Flatpeak-Signature | The signature, prefixed with v1= (e.g. v1=WbyqgN...). |
Flatpeak-Timestamp | Unix timestamp (seconds) when the payload was signed. |
Flatpeak-Key-ID | The kid of the signing key — must match a key in the JWKS. |
Flatpeak-Signature-Scheme | The signature scheme version (currently v1). |
Verifying a signature with OpenSSL
Fetch the public key as PEM
Pull the JWKS, pick the key whose If your JWKS is unavailable but you have the raw DER bytes, wrap them as PEM directly:
kid matches Flatpeak-Key-ID, and convert it to PEM. The cleanest path is via a one-shot Python script (no extra tooling needed beyond python3):Prepare the payload file
The signed message is If If your only copy of the body is pretty-printed, you cannot reliably reconstruct the exact bytes that were signed — capture the raw body next time. As a last resort, if the body is plain JSON without significant whitespace decisions, compact it:
{timestamp}.{raw_body} with no trailing newline.raw_body.json was saved with a trailing newline by an editor, strip it:Decode the signature
Strip the If
v1= prefix and base64url-decode. Convert URL-safe chars and add = padding so the length is a multiple of 4:wc -c reports anything other than 256, the decode is wrong — re-check that you stripped v1= and didn’t include any whitespace.Common pitfalls
| Problem | Symptom | Fix |
|---|---|---|
| Pretty-printed or re-serialized JSON | Verification failure | Use the raw bytes from the original request. Reconstruction is unreliable. |
| Trailing newline in payload file | Verification failure | Check with xxd, remove with truncate -s -1. |
| Base64 padding missing | Verification failure or decode error | The signature is base64url with no padding — add = so the length is a multiple of 4 before standard base64 decoding. |
| Base64 URL-safe chars not converted | Verification failure | Replace - with + and _ with / before standard base64 decoding. |
| Wrong public key | Verification failure | Confirm Flatpeak-Key-ID matches the kid of the JWK you converted to PEM. |
v1= prefix included in decode | Decode error or wrong-length bytes | Strip the v1= scheme identifier before decoding. |
| Wrong PSS salt length | Verification failure | Use -sigopt rsa_pss_saltlen:-1 (digest length, 32 bytes). The default 0 does not match Flatpeak’s signing. |
| Decoded signature isn’t 256 bytes | Verification failure | The decode is malformed — re-check padding, URL-safe chars, and the v1= strip. |

