Azuqe
Go to Dashboard

Integration Guide

JSON Webhook

Receive signed JSON payloads on every blog event. Build your own CMS, automation, or any custom backend.

How It Works

When a blog post is generated, published, updated, or fails, Azuqe sends a signed HTTP POST request to your endpoint. You receive the full blog post data (title, HTML content, slug, SEO metadata, keywords, cover image) plus event metadata. Every delivery includes an HMAC-SHA256 signature so you can verify the payload is authentic and untampered.

Setup Steps

1

Register your endpoint in Azuqe

  • Open your dashboard and go to Connected Apps.
  • Click Webhook, paste your endpoint URL, and click Save.
  • Copy the signing secret (whsec_...) shown once at creation. Store it as an environment variable on your server.
  • Optionally add custom headers (e.g. Authorization: Bearer token) in the headers JSON field.
2

Build a webhook receiver

  • Create a server endpoint that accepts HTTP POST requests with Content-Type: application/json.
  • Your endpoint must return a 2xx status code within 10 seconds. Non-2xx or timeouts trigger retries.
  • Below is a minimal receiver. Use your framework's raw body mode so signature verification works.
webhook-receiver.js
const express = require('express');
const crypto = require('crypto');
const app = express();

// Use express.raw() so the body bytes stay intact for HMAC verification.
// express.json() would parse and re-serialize, breaking the signature.
app.post('/webhooks/azuqe',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const secret = process.env.AZUQE_WEBHOOK_SECRET; // whsec_...
    const sigHeader = req.headers['x-azuqe-signature']; // t=...,v1=...
    const rawBody = req.body.toString('utf-8');

    // 1. Verify HMAC-SHA256 signature
    if (secret && sigHeader) {
      const [tPart, v1Part] = sigHeader.split(',');
      const ts = tPart.replace('t=', '');
      const expected = v1Part.replace('v1=', '');
      const key = secret.startsWith('whsec_') ? secret.slice(6) : secret;
      const computed = crypto
        .createHmac('sha256', key)
        .update(ts + '.' + rawBody)
        .digest('hex');
      if (computed !== expected) {
        return res.status(401).json({ error: 'Invalid signature' });
      }
    }

    // 2. Parse and handle the payload
    const payload = JSON.parse(rawBody);
    console.log('Event:', payload.meta.event_type);
    console.log('Post:', payload.data.blog_post?.title);

    // 3. Process asynchronously, return 200 immediately
    handleEvent(payload).catch(console.error);
    res.status(200).json({ ok: true });
  }
);

async function handleEvent(payload) {
  switch (payload.meta.event_type) {
    case 'blog_post.published':
      // Sync to your CMS, send notification, etc.
      break;
    case 'blog_post.generation_completed':
      // Article generated, optionally auto-publish
      break;
    case 'blog_post.generation_failed':
      // Alert your team
      break;
  }
}

app.listen(3000);
3

Test with a ping delivery

  • Click Send test on the Webhook card in Connected Apps to fire a ping event.
  • Your endpoint should receive a POST with event_type: 'ping' and the test message.
  • Verify that signature verification passes before going live.
4

Subscribe to events

  • In the Webhook settings, select which events you want to receive.
  • Only events you subscribe to will be delivered. No subscription means no delivery for that event type.

Example Payload

Every delivery includes the following JSON body. Fields may be null when data is not available.

POST /your-endpoint
{
  "data": {
    "blog_post": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "slug": "how-to-improve-seo-ranking",
      "title": "How to Improve Your SEO Ranking in 2026",
      "topic": "SEO best practices for small businesses",
      "keyword": "improve seo ranking",
      "outline": "<h2>Understanding Search Intent</h2><h2>On-Page Optimization</h2><h2>Technical SEO Checklist</h2>",
      "language": "en",
      "created_at": "2026-07-27T10:30:00.000Z",
      "updated_at": "2026-07-27T10:32:15.000Z",
      "meta_title": "How to Improve SEO Ranking in 2026 | Complete Guide",
      "meta_description": "Learn proven strategies to boost your Google ranking. Covers on-page SEO, technical audits, and content optimization.",
      "word_count": 2847,
      "article_size": "medium",
      "html_content": "<h1>How to Improve Your SEO Ranking in 2026</h1><p>Search engine optimization remains the most cost-effective way to drive organic traffic...</p>",
      "markdown_content": null,
      "featured_image_url": "https://cdn.azuqe.com/images/seo-guide-2026.webp",
      "featured_image_alt": null,
      "secondary_keywords": ["seo tips", "google ranking factors", "on-page seo"],
      "status": "published"
    },
    "author": {
      "id": "u1234567-89ab-cdef-0123-456789abcdef",
      "email": "you@example.com",
      "first_name": "Jane",
      "last_name": "Doe"
    },
    "generation_type": "one_shot",
    "duration_seconds": 45
  },
  "meta": {
    "event_id": "evt_9f8e7d6c-5b4a-3210-fedc-ba9876543210",
    "event_type": "blog_post.generation_completed",
    "event_version": "1.0",
    "timestamp": "2026-07-27T10:32:15.000Z",
    "organization_id": "org_abc123",
    "workspace_id": "org_abc123",
    "site_id": "site_xyz789",
    "triggered_by": "u1234567-89ab-cdef-0123-456789abcdef"
  }
}

Verify the Signature

Every delivery includes an X-Azuqe-Signature header. Verify it to confirm the payload came from Azuqe and was not tampered with.

const crypto = require('crypto');

function verifyAzuqeSignature(rawBody, signatureHeader, secret) {
  // signatureHeader format: "t=<unix_timestamp>,v1=<hex_signature>"
  const [tPart, v1Part] = signatureHeader.split(',');
  const timestamp = tPart.replace('t=', '');
  const expectedSig = v1Part.replace('v1=', '');

  // Strip whsec_ prefix if present
  const key = secret.startsWith('whsec_') ? secret.slice(6) : secret;

  // Compute HMAC-SHA256(timestamp.body)
  const computed = crypto
    .createHmac('sha256', key)
    .update(timestamp + '.' + rawBody)
    .digest('hex');

  // Timing-safe comparison (pad to equal length to avoid length-mismatch throw)
  const a = Buffer.from(computed, 'hex');
  const b = Buffer.from(expectedSig.padEnd(a.length, '0'), 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Usage in your Express handler:
// const valid = verifyAzuqeSignature(rawBody, req.headers['x-azuqe-signature'], process.env.AZUQE_WEBHOOK_SECRET);

What Gets Synced

blog_post.generation_completed - Article generated with full content, SEO metadata, and outline
blog_post.published - Article published to CMS with live URL
blog_post.updated - Existing article edited and republished
blog_post.generation_failed - Generation failed with sanitized error message
blog_post.publish_failed, blog_post.republished, blog_post.deleted
audit.completed, audit.failed, indexing.submitted, backlink.lost, backlink.discovered, ping

Troubleshooting

Ready to connect?

Open your dashboard and navigate to Connected Apps to link JSON Webhook.

Was this page helpful?

Let us know how we can improve this documentation.