Octopus Cards
Best Practices

Webhooks

Best practices for receiving, verifying, and processing webhooks reliably

Verify Every Webhook

Never trust a webhook based solely on the X-OCTOPUS-WEBHOOK-TOKEN header. Always compute the HMAC-SHA256 signature of the raw request body using your secret and compare it against the X-Signature header. This proves both authenticity (it came from Octopus) and integrity (the body hasn't been tampered with).

Use constant-time comparison (e.g. hmac.Equal in Go, crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing-based side-channel attacks.

See the Signature Verification page for full implementation examples.

Validate Timestamps

Check the X-Timestamp header and reject webhooks older than 5 minutes. This prevents replay attacks where an attacker resends a captured webhook request.

ts, _ := strconv.ParseInt(r.Header.Get("X-Timestamp"), 10, 64)
if time.Since(time.Unix(ts, 0)) > 5*time.Minute {
    http.Error(w, "Webhook too old", 401)
    return
}

Process Idempotently

The same event may be delivered more than once due to retries. Always deduplicate by X-Event-ID:

  1. Before processing, check if you've already handled this event ID
  2. If yes, return 200 OK without re-processing
  3. If no, process the event and record the ID

Use a database table or cache (e.g. Redis SET NX with a TTL) to track processed event IDs.

Respond Quickly

Your endpoint must return a 2xx response within 30 seconds. If it times out or returns a non-2xx status, the delivery is retried.

Do not perform heavy processing inline. Instead:

  1. Receive the webhook and validate the signature
  2. Persist the raw payload to a queue or database
  3. Return 200 OK immediately
  4. Process the event asynchronously in a background worker
func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    // 1. Verify signature (fail fast)
    if !verifySignature(body, r.Header.Get("X-Signature"), secret) {
        http.Error(w, "Invalid signature", 401)
        return
    }

    // 2. Queue for async processing
    queue.Enqueue("webhook_events", body)

    // 3. Respond immediately
    w.WriteHeader(http.StatusOK)
}

Secure Your Secret

  • Store your webhook secret in environment variables or a secrets manager — never in source code
  • Rotate your secret periodically by updating it in both your dashboard and your application
  • If you suspect your secret has been compromised, rotate it immediately

Handle Retries Gracefully

Octopus retries failed deliveries up to 5 times with exponential backoff (1m, 2m, 4m, 8m, 16m). Design your endpoint to handle retries:

  • Return 200 OK for duplicate events (idempotent processing)
  • Return 200 OK even if you don't recognise the event type (forward compatibility)
  • Only return non-2xx if you genuinely cannot accept the webhook (e.g. signature invalid)

Returning a 4xx or 5xx response triggers a retry. If your handler has a bug that always returns an error, you will receive the same webhook 6 times. Fix errors promptly and return 200 for events you don't need to process.

Use HTTPS

Webhook URLs must use HTTPS. This ensures the payload (which may contain order details) is encrypted in transit. Self-signed certificates are not supported.

Monitor Delivery

Check your dashboard for failed webhook deliveries. Common issues:

  • Timeouts — your endpoint is too slow. Offload processing to a background queue.
  • SSL errors — your certificate has expired or is misconfigured.
  • Connection refused — your server is down or firewall is blocking Octopus IPs.
  • Non-2xx responses — your handler is returning an error. Check your application logs.

Checklist

  • Verify X-Signature using HMAC-SHA256 with constant-time comparison
  • Validate X-Timestamp to reject stale webhooks (> 5 minutes)
  • Deduplicate by X-Event-ID for idempotent processing
  • Respond within 30 seconds with 200 OK
  • Process heavy logic asynchronously (queue + worker)
  • Store secret in environment variables, not source code
  • Return 200 OK for unrecognised event types
  • Monitor delivery failures in your dashboard

On this page