This guide explains how to configure and use webhooks to receive notifications and data about events happening in your project, such as when a blog post has been generated and is ready for publishing. By setting up a webhook endpoint, you can integrate our platform directly with your own systems or workflows.
This guide covers:
To start receiving webhooks, you first need to enable the Webhook publishing integration for your project:
Note: Only one publishing integration method can be active at a time. Enabling webhooks will disable any other active integration (e.g., WordPress, Wix).
Once the Webhook integration is enabled, you can manage the specific endpoints where notifications will be sent.
You can configure multiple endpoints if needed, although typically one is sufficient.
Our system sends webhooks for specific events. Currently, the primary event is:
BLOG_POST_GENERATED
: Sent when a blog post has finished the generation process and is ready for review or publishing.All webhook requests are sent as HTTP POST requests with a JSON body (Content-Type: application/json
). The payload follows this general structure:
{ "metadata": { "event_type": "BLOG_POST_GENERATED", // The type of event "timestamp": "2023-10-27T10:00:00.123Z", // ISO 8601 timestamp of when the event occurred "webhook_id": "...", // Unique ID for this specific delivery attempt }, "data": { // Event-specific data structure for BLOG_POST_GENERATED "blog_post": { "id": "clxka123b0001abcd1efghijk", "headline": "Your Awesome Blog Post Headline", "slug": "your-awesome-blog-post-headline", "targetedKeywords": ["targeted keyword 1", "targeted keyword 2"], "mainImageUrl": "https://your-image-host.com/image.jpg", "createdAt": "2023-10-27T09:58:00.000Z", "updatedAt": "2023-10-27T10:00:00.000Z", "projectId": "clxka456d0002bcde2fghijlk", "projectName": "My Awesome Project", "projectWebsiteUrl": "https://my-awesome-project.com", "markdownContent": "# Your Awesome Blog Post Headline\n\nThis is the introduction...\n\n## Section 1\n\nContent for section 1...\n\n### Sub-section 1.1\n\nMore details...\n\n## Conclusion\n\nFinal thoughts...", "htmlContent": "<h1 style=\"font-size: 2rem; font-weight: bold; margin: 2rem 0 1rem 0;\">Your Awesome Blog Post Headline</h1><div style=\"margin: 0 0 1rem 0; line-height: 1.6;\">This is the introduction...</div><h2 style=\"font-size: 1.5rem; font-weight: bold; margin: 1.5rem 0 0.75rem 0;\">Section 1</h2><div style=\"margin: 0 0 1rem 0;\">Content for section 1...</div><!-- CTA section with inline CSS --><div style=\"background-color: #f8fafc; border-radius: 12px; padding: 2rem; text-align: center; margin: 2rem 0;\">...</div><h2 style=\"font-size: 1.5rem; font-weight: bold;\">Conclusion</h2><div style=\"margin: 0 0 1rem 0;\">Final thoughts...</div>", "metaDescription": "An awesome meta description for SEO.", "metaKeywords": ["keyword1", "keyword2", "keyword3"], "ctaData": { "headline": "Ready to Get Started?", "subheadline": "Join thousands of satisfied customers today", "buttonText": "Get Started Now", "buttonUrl": "https://my-awesome-project.com/signup", "styling": { "section": { "backgroundColor": "#f8fafc", "borderRadius": 12, "shadow": "sm" }, "headline": { "fontSize": 24, "fontWeight": "bold", "underline": false, "italic": false, "color": "#1a1a1a", "alignment": "center" }, "subheadline": { "fontSize": 16, "fontWeight": "normal", "underline": false, "italic": false, "color": "#666666", "alignment": "center" }, "button": { "text": { "fontSize": 16, "fontWeight": "bold", "underline": false, "italic": false, "color": "#ffffff" }, "backgroundColor": "#3b82f6", "borderRadius": 8, "shadow": "md", "alignment": "center" } } } } } }
(Note: The exact fields within data.blog_post
may evolve. Rely on the fields present in the actual payload.)
Each webhook POST request includes the following headers:
Content-Type
: application/json
User-Agent
: Jenna-Webhooks/1.0
(Identifies the request source)X-Webhook-ID
: The unique identifier for this specific webhook delivery attempt (same as metadata.webhook_id
in the payload).X-Signature
: A SHA-256 HMAC signature of the raw request body, generated using your Webhook Signing Secret.To ensure the security and authenticity of webhooks, you must verify the X-Signature
header. This confirms the request originated from our system and wasn't tampered with.
Verification Steps:
X-Signature
header.X-Signature
header with the signature you generated. If they match, the webhook is valid.const crypto = require('crypto') export const POST = async (req : Request) => { // validate signature const rawBody = await new Response(req.body).text(); const secret = process.env.JENNA_SIGNING_SECRET const hmac = crypto.createHmac('sha256', secret) const digest = Buffer.from(hmac.update(rawBody).digest('hex'), 'utf8'); const signature = Buffer.from(req.headers.get('X-Signature') || '', 'utf8'); if (!crypto.timingSafeEqual(digest, signature)) { throw new Error('Invalid signature'); } // process the webhook //... return new Response(null, { status: 200 }) }
If your endpoint fails to respond with a 2xx
status code (e.g., 200 OK
) within 30 seconds, or if there's a network error preventing delivery, our system will automatically retry sending the webhook.
attempt
Metadata: The metadata.attempt
field in the payload indicates which delivery attempt it is (1 for the first, 2 for the second, etc.).Your endpoint should be idempotent, meaning receiving the same webhook multiple times should not cause duplicate actions or errors. Verifying the X-Webhook-ID
can help with deduplication if needed.
200 OK
Quickly: Acknowledge receipt of the webhook by returning a 2xx
status code as soon as possible (ideally within a few seconds). Perform complex processing asynchronously in the background. Timeouts will cause retries.X-Signature
before trusting the payload.crypto.timingSafeEqual
(Node.js), hmac.compare_digest
(Python), or hash_equals
(PHP).Common issues and solutions:
200 OK
.If you encounter persistent issues implementing webhook integration:
X-Webhook-ID
of a failed delivery, error messages from your logs, and relevant code snippets.Stop wrestling with content creation. Your dedicated AI writer generates SEO-optimized blog posts that drive traffic, so you can focus on building your business.
Get More SEO Traffic