API ReferenceWebhooks
Signature Verification
Verify webhook authenticity using HMAC-SHA256 signatures
Every webhook request includes an X-Signature header containing an HMAC-SHA256 signature of the request body. Verify this signature to ensure the webhook was sent by Octopus Cards and has not been tampered with.
How Signing Works
- Octopus Cards computes
HMAC-SHA256(request_body, your_secret) - The result is hex-encoded and sent in the
X-Signatureheader - You compute the same HMAC on the received body using your secret
- If the signatures match, the webhook is authentic
Verification Headers
| Header | Purpose |
|---|---|
X-Signature | HMAC-SHA256 hex digest of the raw request body |
X-OCTOPUS-WEBHOOK-TOKEN | Your shared secret (for quick lookup) |
X-Timestamp | Unix timestamp - use to reject stale webhooks |
X-Event-ID | Unique event ID - use for idempotency |
Implementation Examples
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Read the raw body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// Get the signature from the header
receivedSig := r.Header.Get("X-Signature")
secret := "your_webhook_secret"
// Compute expected signature
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expectedSig := hex.EncodeToString(mac.Sum(nil))
// Constant-time comparison to prevent timing attacks
if !hmac.Equal([]byte(receivedSig), []byte(expectedSig)) {
http.Error(w, "Invalid signature", 401)
return
}
// Signature valid - process the event
w.WriteHeader(http.StatusOK)
}Timestamp Validation
Use the X-Timestamp header to reject stale or replayed webhooks. A common approach is to reject webhooks older than 5 minutes:
timestamp := r.Header.Get("X-Timestamp")
ts, _ := strconv.ParseInt(timestamp, 10, 64)
age := time.Since(time.Unix(ts, 0))
if age > 5*time.Minute {
http.Error(w, "Webhook too old", 401)
return
}