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:
- Deploy your endpoint to accept signatures from both the old and new secret
- Rotate the secret in Kit
- Verify deliveries succeed with the new secret
- 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_attimestamp
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
2xxstatus 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