Article Webhooks
GeoCopy can POST signed JSON callbacks to your HTTPS endpoint when article lifecycle events occur. Configure webhooks from Portal → Integrations → Webhook, or manage an existing connection at Portal → Integrations → Webhook settings.
Setup
When you connect a webhook, you configure:
- Endpoint URL — a public HTTPS URL (localhost and private IPs are blocked).
- Signing secret — used to verify payloads. Click Generate for a 64-character hex secret, or paste your own. GeoCopy stores the secret encrypted server-side.
- Subscribed events — choose any combination of the four article events below.
- Payload version — Full article document (default) or Minimal metadata only.
After saving, use Connect & test (connect dialog) or Send test event (settings page) to confirm your endpoint receives and verifies a signed payload.
Event types
| Event | When it fires |
|---|---|
article.generated | Article content generation completed (manual approval mode). |
article.ready_for_review | Article finished the pipeline and is awaiting approval before publish (status: awaiting_approval). |
article.published | Article was published to a CMS or marked published. |
article.failed | Article generation or publish failed after retries. |
By default, all four events are subscribed. You can change subscriptions anytime on the webhook settings page.
Request format
GeoCopy sends an HTTP POST to your endpoint URL.
Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Signature | sha256=<hex> HMAC of the raw request body |
X-Timestamp | ISO 8601 timestamp (same value as timestamp in the body) |
X-Event | Event name, e.g. article.published |
Payload versions
Full (default): the complete article document (PublicArticleV2) plus event and timestamp. Includes meta, content, images, links, and optional billing fields. See API docs → Webhooks for the full schema.
Minimal: compact metadata only:
{
"event": "article.published",
"articleId": "uuid",
"title": "Article title",
"status": "published",
"timestamp": "2026-06-05T12:00:00.000Z",
"externalUrl": "https://example.com/blog/post",
"primaryKeyword": "target keyword"
}Verifying signatures
Recompute HMAC-SHA256 over the raw request body using your signing secret. Compare the result to X-Signature in constant time. Always verify before running business logic.
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhookSignature(
payload: string,
secret: string,
signatureHeader: string
): boolean {
const expected =
"sha256=" + createHmac("sha256", secret).update(payload).digest("hex");
if (expected.length !== signatureHeader.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}Reject requests when verification fails. Optionally reject replays by checking X-Timestamp is within a short window (for example, 5 minutes).
Delivery and retries
- At-least-once delivery — the same event may arrive more than once. Deduplicate on
(event, articleId). - HTTP retries — up to 4 attempts per dispatch with exponential backoff (1s, 2s, 4s, capped at 16s) on network errors and 5xx responses.
- Inngest retries — the full dispatch job may retry up to 5 times if delivery still fails.
- 4xx responses — treated as permanent failure for that attempt; GeoCopy does not retry HTTP for 4xx.
- 2xx responses — acknowledged as received.
- Timeout — 10 seconds per request attempt.
Handler checklist
- Verify
X-Signatureusing the raw body bytes. - Parse JSON from the same raw body used for verification.
- Handle idempotently (store processed event keys).
- Return
2xxquickly; defer heavy work to a background queue. - Return
5xxonly when you want GeoCopy to retry.
Test event
Use Connect & test in the connect dialog or Send test event on the webhook settings page. The test payload uses article.published with a synthetic article ID and status: "test".
Delivery history
Open webhook settings to view delivery history: event type, status, attempts, errors, and timestamps for recent deliveries.
For a shorter reference inside the developer docs, see Portal → API docs → Webhooks.