Webhook security
Verify Due webhook signatures using the endpoint's Ed25519 public key.
Verify every webhook signature before you process the event payload.
Store the endpoint public key
When you create a webhook endpoint, the API returns a public key in PEM format. Store this key securely; each webhook endpoint has its own unique key pair.
{
"id": "whk_2l6qY9nCeKcyXD",
".....",
"publicKey": "{webhook_endpoint_public_key}"
}Verify each webhook request
Due signs webhook payloads using Ed25519. The X-Webhook-Signature HTTP header contains a hex-encoded signature generated from the raw request body.
- Read the request body exactly as received.
- Read and hex-decode the
X-Webhook-Signatureheader. - Verify the decoded signature against the raw body with the stored PEM public key.
- Process the event only when verification succeeds. Reject requests with a missing, malformed, or invalid signature.
Do not parse, reformat, or otherwise modify the request body before verification. Any change to the payload causes signature verification to fail.
Go example
This example parses the PEM-encoded Ed25519 public key and verifies the raw request body.
import (
"crypto/ed25519"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
)
var publicKeyPEM = "WEBHOOK_PUBLIC_KEY_PEM"
func Verify(requestData []byte, signatureHex string) bool {
publicKey, err := parseEd25519PublicKeyFromPEM(publicKeyPEM)
if err != nil {
return false
}
signature, err := hex.DecodeString(signatureHex)
if err != nil {
return false
}
return ed25519.Verify(publicKey, requestData, signature)
}
func parseEd25519PublicKeyFromPEM(pemStr string) (ed25519.PublicKey, error) {
block, _ := pem.Decode([]byte(pemStr))
if block == nil {
return nil, fmt.Errorf("invalid PEM: no block found")
}
pubAny, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pub, ok := pubAny.(ed25519.PublicKey)
if !ok {
return nil, fmt.Errorf("PEM is not an Ed25519 public key (got %T)", pubAny)
}
return pub, nil
}Verify returns true for a valid signature and false when the public key or signature cannot be parsed, or when the signature does not match the raw request body.
Verify retried events
A manual retry sends the same payload and signs it in the same way as the original delivery. Verify retried events with the public key stored for that webhook endpoint using the same process.
Updated 16 days ago