---
title: "Webhook security"
description: "Verify exact raw HMAC bytes, reject stale or duplicate events and coordinate endpoint-secret rotation safely."
slug: "/docs/v1/security/webhook-security"
section: "Security"
order: 23
audience: "all"
level: "advanced"
last_updated: "2026-10-17"
version: "v1"
related: ["/docs/v1/security/signed-responses","/docs/v1/security/threat-model"]
draft: false
---
Treat a [webhook](/docs/v1/start/glossary#webhook) as a signed notification, not as the authority that decides whether a [license](/docs/v1/start/glossary#license) is valid.

## What you will learn
- Verify the sender's exact signature format.
- Apply receiver timestamp and deduplication rules.
- Rotate secrets without assuming an overlap.

## Prerequisites
- Read [authentication](/docs/v1/start/authentication).
- Use an HTTPS backend endpoint that can read the exact raw request body.
- Keep the endpoint secret on your server only.

## Exact sender contract
| Item | Actual value |
| --- | --- |
| Method | POST to your configured HTTPS endpoint |
| Content-Type | application/json |
| Event ID header | X-LMP-Event-ID |
| Event name header | X-LMP-Event |
| Signature header | X-LMP-Signature: t=UNIX_SECONDS,v1=LOWERCASE_HEX_HMAC |
| Signed message | decimal timestamp + a literal period + exact raw body bytes |
| Algorithm | HMAC-SHA256 with the endpoint's plaintext secret |
| Body | id, event, created_at, environment and data |
The X-LMP header names preserve existing compatibility identifiers. This signature is not Ed25519 and is not Stripe's signing format. Do not parse/reserialize the body before checking it.

## Receiver checks
1. Reject oversized bodies before buffering them.
2. Parse one supported timestamp/signature pair from the signature header.
3. Require a numeric Unix timestamp within your chosen tolerance.
4. Recompute HMAC-SHA256 over timestamp.period.raw_body with your endpoint secret.
5. Compare fixed-length signature bytes using a constant-time operation.
6. Parse the authenticated JSON and verify its event ID, environment and expected resource ownership.
7. Atomically record the unique event ID together with the intended local business change.
8. Return a 2xx response quickly after the action is durable.
Expected result: altered and stale messages fail; duplicate deliveries do not repeat the business effect.

A 300-second timestamp tolerance is receiver guidance, not a sender-enforced inbound gate. Keep your server clock synchronized and choose your own justified tolerance. Timestamp checks alone do not stop duplicates inside that window.

## Retries and ordering
The sender attempts at most 6 deliveries. Delays after attempts 1–5 are60, 300, 1800, 7200 and 43200 seconds, measured after failure handling. A 2xx response with no transport error means success. Lost acknowledgements can repeat [delivery](/docs/v1/start/glossary#delivery); ordering is not guaranteed.

The timestamp/signature is generated for each attempt, while the event ID and stored body remain stable. Deduplicate by authenticated event ID, not signature or delivery timestamp. Your unique-record/business-action transaction is the receiver's responsibility.

## Endpoint and secret protection
Use HTTPS with a valid certificate. The sender validates public DNS/IP targets, verifies TLS, does not follow redirects and applies5-second connect /12-second total timeouts. These sender controls do not replace authentication on your receiver.

There is no published fixed outbound IP range you can safely invent for an allowlist. Use HMAC verification as the primary sender-authentication control. If you add network filtering, obtain confirmed operational ranges and retain signature checks.

Endpoint secrets are hashed and encrypted for server delivery. Rotation replaces the active secret immediately; old queued events use the new current secret on their next attempt. No secret-version header or automatic overlap is implemented. Coordinate the receiver's secret update and securely remove retired values. Disabling an endpoint pauses queued attempts rather than erasing them.

## Reference boundary
This page is the complete security summary. The detailed event/delivery reference is assigned to P8 and remains unpublished until tested. Its prepared navigation target will activate when that phase publishes it; this page does not send readers to a broken or authenticated placeholder.

[Detailed webhook signature reference](/docs/v1/guides/verify-webhook-signatures)

## Verify your receiver
1. Send a signed synthetic test body from your own backend test harness.
2. Change one body byte and confirm refusal.
3. Use an old/future timestamp beyond the chosen tolerance and confirm refusal.
4. Send the same authenticated event ID twice and confirm one durable business effect.
5. Simulate an acknowledgement loss and confirm retries remain harmless.
6. Rotate only a test endpoint secret and confirm the intended coordinated update.
Expected result: receiver behavior is tested without third-party request catchers or production data. Complete receiver sample applications and event payload reference belong to P8, not an untested claim here.

## Expected result
A receiver validates authentic recent bytes and applies each authorized event once in its own durable transaction.

## Common mistakes
- Verifying a reserialized body.
- Trusting the event-name header without matching authenticated body data.
- Treating a license webhook or checkout return as the license-validity authority.

## Security notes
Do not include secrets in webhook URLs or public request-catcher tools. Notifications can be delayed; use the licensing API and verified state for access decisions.

## Troubleshooting
| Symptom | Cause | Action |
| --- | --- | --- |
| HMAC differs | Body bytes or secret changed | Verify the original raw body/current secret |
| Duplicate business action | No durable event-ID deduplication | Make the action and dedupe record atomic |
| Queued event fails after rotation | Sender uses the new secret immediately | Coordinate receiver configuration |

## Next steps
Read [signed responses](/docs/v1/security/signed-responses) and [the threat model](/docs/v1/security/threat-model), then validate your own integration in [Sandbox](/docs/v1/start/glossary#sandbox).

## Related
- [Verification examples](/docs/v1/security/verify-ed25519)
- [Offline mode](/docs/v1/security/offline-mode)
- [Security and disclosure](/security)
