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

  1. Octopus Cards computes HMAC-SHA256(request_body, your_secret)
  2. The result is hex-encoded and sent in the X-Signature header
  3. You compute the same HMAC on the received body using your secret
  4. If the signatures match, the webhook is authentic

Verification Headers

HeaderPurpose
X-SignatureHMAC-SHA256 hex digest of the raw request body
X-OCTOPUS-WEBHOOK-TOKENYour shared secret (for quick lookup)
X-TimestampUnix timestamp - use to reject stale webhooks
X-Event-IDUnique 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
}

On this page