Receiving Webhooks
Receive webhook deliveries from Logspot, verify they came from us, and handle retries correctly.
A webhook destination sends an HTTP request to your endpoint when an action fires. This page is for whoever builds the receiving end.
To create one, add a Send Notification action and choose a webhook destination. For the opposite direction, sending events into Logspot, see Inbound Webhooks.
What We Send
Every delivery is a POST with a JSON body and these headers:
| Header | Meaning |
|---|---|
x-logspot-event-type | The event that fired the webhook |
x-logspot-delivery-id | Unique id for this delivery, stable across retries |
x-logspot-signature | Hex HMAC-SHA256 proving the request came from Logspot |
x-logspot-signature-version | The signing scheme, v1 or legacy |
x-logspot-timestamp | Unix seconds when we signed, sent with v1 only |
Content-Type | application/json |
The default body names the event and the project:
{ "event": "Signup Completed", "projectId": "proj_123" }If you configure a custom payload, that object is sent verbatim instead.
Custom headers you configure are sent too. They are merged before Logspot's signing headers, so a custom
header cannot override the security headers. It can override Content-Type or x-logspot-event-type.
Your Signing Secret
Each webhook has its own secret, shown in the Signing Secret field when you set the webhook up. Treat it like a password, because anyone holding it can forge deliveries.
Editing a webhook's URL, headers, or payload does not change its secret. To roll one, recreate the webhook destination so a new secret is minted, then update your receiver. Deliveries sign with the new secret immediately, so plan for a brief overlap.
Verifying the Signature
Compute the HMAC yourself and compare it against x-logspot-signature.
For v1, which every new webhook uses, sign the string "{timestamp}.{body}", where the timestamp is
the x-logspot-timestamp header and the body is the raw request bytes. For legacy, used by webhooks
carried over from the older trigger system, sign the raw body alone and expect no timestamp header.
The body must be the exact bytes you received. A re-serialized JSON object will not match.
Reject v1 requests whose timestamp is too old. Five minutes is a sensible tolerance, and it is what
stops a captured request being replayed later. Always compare with a constant-time comparison.
import crypto from 'node:crypto';
// `rawBody` must be the exact bytes received, e.g. via express.raw()
// or bodyParser's verify hook. A re-stringified object will NOT match.
export function verifyLogspotWebhook(headers, rawBody, secret) {
const signature = headers['x-logspot-signature'];
if (!signature) return false;
const version = headers['x-logspot-signature-version'];
const signedPayload =
version === 'v1' ? `${headers['x-logspot-timestamp']}.${rawBody}` : rawBody;
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
const a = Buffer.from(signature, 'hex');
const b = Buffer.from(expected, 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
if (version === 'v1') {
const ageSeconds = Math.abs(
Date.now() / 1000 - Number(headers['x-logspot-timestamp']),
);
if (ageSeconds > 300) return false; // replay window: 5 minutes
}
return true;
}Delivery Behavior
Respond with a 2xx quickly. Anything else counts as a failure, and we time out after five seconds.
Acknowledge first, then do slow work.
Failures are retried on a backoff of roughly one minute, five minutes, thirty minutes, two hours, and eight hours. That is five retries across about ten and a half hours, after which the delivery is marked failed in the Actions activity log. Configuration errors such as an unreachable or blocked URL are not retried and fail immediately.
Deliveries are at-least-once. A retry can race a slow success, so duplicates are possible. Dedupe on
x-logspot-delivery-id, which stays the same across every attempt of the same delivery.
There is no ordering guarantee, so do not assume deliveries arrive in event order.
Targets must be publicly reachable. URLs resolving to private or internal networks are refused both when the webhook is saved and when it would be delivered.
Related
In-App Insights (Beta)
Embed logspot analytics in your application or website.
Connect Claude & AI Assistants (MCP)
Ask Claude (and other MCP-compatible AI tools) questions about your Logspot analytics — events, users, funnels, retention, revenue, companies, and profiles — over a secure, read-only connection.