Skip to main content
Go to Settings, then Webhooks to hook Notra up to n8n or any other public HTTPS endpoint. Pick the events you want and copy the signing secret you get after creating the endpoint. Owners and admins can manage endpoints and retry failed deliveries, while members can see the delivery history.

Events

You get post.generation.* events for tracked post generation jobs, including the ones you start with POST /v1/posts/generate, and the first terminal result for each tracked generation job wins.

Post lifecycle

Post lifecycle events all have the same data shape: { "postId": "post_123" }. post.created fires when a post is inserted. post.updated fires when persisted post fields actually change, including status changes, but not for timestamp-only updates. post.published fires when a post is inserted as published and every time a draft becomes published, including after unpublishing. Leaving published status emits post.unpublished, and any status transition also emits post.updated. Deleting a post emits post.deleted, including when deleting its collection cascades to the post. Deleting the organization does not emit post deletion events. Database triggers write these events in the same transaction as the post change, which covers API, dashboard, AI and GitHub writes, so a failed event insert rolls back the post change.

GEO scan results

The first persisted terminal result for a scan ID emits either geo.scan.completed or geo.scan.failed, whether the scan is inserted as terminal or transitions to terminal. Later metadata changes and repeated terminal updates do not emit another event, even if the reported outcome changes. Both events contain string scanId and projectId fields. Completed events also contain runId (string | null) and checksTotal, checksFailed, mentions and durationMs (each number | null). Failed events also contain errorCode, error and failedStage (each string | null) and retryable (boolean | null). Every field is always present, with null for unavailable values. Notra writes the event in the same transaction as the scan result. Failure text is bounded, with errorCode limited to 256 characters and error to 4096 characters. Polling remains available for scan progress.

Payload version

New events use apiVersion: "2026-10-06" to identify the repeat-publish semantics. The post.published data payload remains { "postId": "post_123" }, and existing stored events keep their original payload and version when retried.

Subscribe through the API

Use an organization API key with the webhooks.write scope:
The response has the endpoint and a one-time secret. Save the secret somewhere safe because you won’t see it again. GET /v1/webhooks lists your subscriptions. An endpoint can subscribe to between one and twelve event types. Subscriptions receive only future matching events, so Notra does not backfill existing posts, jobs and scans. DELETE /v1/webhooks/{endpointId} removes one and cancels anything not sent yet, although a request that’s already on its way can still reach the deleted endpoint.

Verify requests

Read the body as raw text before you parse the JSON. Every request has these headers:
  • x-notra-event: the event type
  • x-notra-event-id: a stable event ID
  • x-notra-delivery-id: a stable delivery ID for this endpoint
  • x-notra-timestamp: when we signed it, in Unix seconds
  • x-notra-signature: v1, followed by a base64 HMAC-SHA256 signature
Strip the whsec_ prefix from your secret, base64-decode the rest and check the HMAC over this UTF-8 message:
Compare signatures in constant time and reject timestamps more than five minutes off in either direction. Answer with a 2xx quickly and push slow work into your own queue. Delivery is at least once, so dedupe by delivery ID, or by event ID if several subscriptions should only trigger one action on your side. Order isn’t guaranteed.

Logs and retries

The delivery table shows the status, response code, attempt count and when each delivery was created. Click a row to see the payload, the destination, the event and delivery IDs, how each attempt went and when the next retry is due. You can retry a failed delivery after a one-minute cooldown, and earlier attempts stay in the history. For API access, use webhooks.read:
  • GET /v1/webhooks/deliveries?offset=0&status=all returns 25 rows and hasMore.
  • GET /v1/webhooks/deliveries/{deliveryId} returns the payload and attempts.
  • POST /v1/webhooks/deliveries/{deliveryId}/retry queues a failed delivery and needs webhooks.write.
We retry network errors, timeouts, 408, 429 and 5xx responses up to eight times, and any other failed response stops delivery, redirects included. Requests time out after ten seconds. Backoff starts at 30 seconds, respects Retry-After and goes up to six hours. Dispatch and recovery run once a minute, so a delivery or retry can start a little after its scheduled time. Finished deliveries stay in the history for 30 days, and anything still in progress stays until it’s done. We don’t store response bodies. Your endpoint has to use a public HTTPS hostname on port 443.
Last modified on October 8, 2026