Logo StartupKit
Integrations

Webhook Security & Delivery

Verify webhook signatures, understand retry behavior, and troubleshoot delivery issues.

Why It Matters

Signature verification proves payloads are authentic and unmodified. Understanding retry and auto-disable behavior helps you build resilient integrations.

Verifying Signatures

Every delivery is signed with your endpoint’s signing secret using HMAC-SHA256. The signature covers the timestamp and request body to prevent replay attacks.

The signed content is the ISO 8601 timestamp and JSON body joined with a period:

<timestamp>.<body>

Ruby

timestamp = request.headers["X-Webhook-Timestamp"]
signature = request.headers["X-Webhook-Signature"]
body = request.body.read

expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, "#{timestamp}.#{body}")

if ActiveSupport::SecurityUtils.secure_compare(expected, signature)
  # Signature is valid
else
  head :unauthorized
end

Node.js

const crypto = require("crypto");

function verifySignature(signingSecret, timestamp, body, signature) {
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${timestamp}.${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Python

import hmac
import hashlib

def verify_signature(signing_secret, timestamp, body, signature):
    expected = hmac.new(
        signing_secret.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)

Preventing Replay Attacks

Compare X-Webhook-Timestamp against the current time and reject requests older than 5 minutes.

Rotating Your Signing Secret

Click Rotate Secret on the webhook detail page to generate a new signing secret. The old secret is immediately invalidated.

To rotate without downtime:

  1. Deploy your endpoint to accept signatures from both the old and new secret
  2. Rotate the secret in Kit
  3. Verify deliveries succeed with the new secret
  4. Remove the old secret from your endpoint

Delivery & Retries

Kit attempts delivery up to 5 times when your endpoint is unreachable or returns an error.

Attempt Behavior
1 Immediate
2–5 Polynomially increasing wait times
Setting Value
Connect timeout 10 seconds
Read timeout 15 seconds

A 2xx response marks the delivery as completed. Any other status code or a timeout marks it as errored and triggers a retry.

Automatic Disabling

After 15 consecutive failed deliveries across any events, Kit disables the webhook automatically.

When disabled:

  • No further deliveries are attempted
  • Status changes to Disabled with a disabled_at timestamp

Click Resume to re-enable. This resets the failure counter and restores the webhook to active.

Delivery Logs

Each webhook endpoint has a Deliveries tab showing the 50 most recent deliveries. Each entry includes:

Field Description
Event Event type that triggered the delivery
Status pending, in_progress, completed, or errored
Attempts Number of delivery attempts
Request headers Headers sent with the request
Response HTTP status code or error message
Timestamp When the delivery was created

Delivery Statuses

Status Meaning
pending Created, waiting to be sent
in_progress Currently being delivered
completed Endpoint returned a 2xx response
errored All retries exhausted or endpoint returned an error

Data Retention

Delivery logs are retained for 30 days and then automatically deleted. Store payloads on your end if you need longer retention.

URL Requirements

  • HTTPS required — HTTP endpoints are rejected
  • Public IPs only — URLs resolving to private addresses (localhost, 10.x.x.x, 192.168.x.x, etc.) are blocked
  • URLs are validated at creation time and before each delivery

Troubleshooting

Issue Cause Fix
Signature mismatch Wrong signing secret or body modified before verification Verify against the raw request body with the current secret
Deliveries timing out Endpoint takes longer than 15 seconds Return 200 immediately and process asynchronously
Webhook auto-disabled 15 consecutive failures Fix your endpoint, then click Resume
URL rejected Private IP or HTTP URL Use an HTTPS URL that resolves to a public IP
Events not firing Webhook paused or event type not subscribed Check webhook status and subscribed events

Quick Checklist

  • Copy your signing secret from the webhook detail page
  • Implement HMAC-SHA256 signature verification in your endpoint
  • Reject requests with timestamps older than 5 minutes
  • Return a 2xx status code within 15 seconds
  • Handle retries idempotently (you may receive the same event more than once)
  • Monitor the delivery logs for errors
  • Set up alerts for when a webhook is auto-disabled

Type to search...