Skip to content

API Events

Overview

The Gold Standard Module API provides event notifications through webhooks, allowing external systems to react to changes in real-time. This document covers the webhook system, event payloads, and integration patterns.

Webhook Architecture

flowchart TB
    subgraph "Event Flow"
        A[Domain Event] --> B[Event Handler]
        B --> C[Webhook Dispatcher]
        C --> D{Queue}
        D --> E[HTTP POST to Webhook URL]
        E --> F{Response}
        F -->|Success| G[Mark Delivered]
        F -->|Failure| H[Retry Queue]
        H --> D
    end

Webhook Configuration

Registering Webhooks

Webhooks can be registered via the Admin panel or API:

POST /api/v1/webhooks
Content-Type: application/json
Authorization: Bearer {token}

{
    "url": "https://your-domain.com/webhook/goldstandard",
    "events": [
        "article.created",
        "article.published",
        "article.updated",
        "article.deleted",
        "comment.created",
        "comment.approved"
    ],
    "secret": "your-webhook-secret",
    "active": true
}

Response

{
    "data": {
        "id": "01HXK5PWGM3QZXN8VBCD9EF2GH",
        "type": "webhook",
        "attributes": {
            "url": "https://your-domain.com/webhook/goldstandard",
            "events": [
                "article.created",
                "article.published"
            ],
            "active": true,
            "created_at": "2026-01-15T10:30:00Z"
        }
    }
}

Event Types

Article Events

Event Description Trigger
article.created New article created (draft) Article saved for first time
article.published Article made public Status changed to published
article.updated Article content modified Any field updated
article.deleted Article removed Article deleted (soft or hard)
article.archived Article moved to archive Status changed to archived
article.featured Article marked as featured Featured flag toggled

Category Events

Event Description Trigger
category.created New category added Category created
category.updated Category modified Category details changed
category.deleted Category removed Category deleted
category.reordered Category order changed Sort order updated

Comment Events

Event Description Trigger
comment.created New comment posted Comment submitted
comment.approved Comment approved by moderator Status changed to approved
comment.rejected Comment rejected Status changed to rejected
comment.deleted Comment removed Comment deleted

User Events

Event Description Trigger
user.subscribed User subscribed to notifications Subscription created
user.unsubscribed User unsubscribed Subscription removed

Event Payloads

Common Payload Structure

All webhook payloads follow this structure:

{
    "id": "evt_01HXK5PWGM3QZXN8VBCD9EF2GH",
    "type": "article.published",
    "created_at": "2026-01-15T10:30:00Z",
    "data": {
        // Event-specific data
    },
    "metadata": {
        "module": "goldstandard",
        "version": "1.0",
        "site_url": "https://your-site.com"
    }
}

Article Published Event

{
    "id": "evt_01HXK5PWGM3QZXN8VBCD9EF2GH",
    "type": "article.published",
    "created_at": "2026-01-15T10:30:00Z",
    "data": {
        "article": {
            "id": "01HXK5MWDJ6QZXN8VBCD9EF2GH",
            "title": "Getting Started with XOOPS 2026",
            "slug": "getting-started-xoops-2026",
            "excerpt": "Learn how to set up and configure XOOPS 2026...",
            "url": "https://your-site.com/modules/goldstandard/article/getting-started-xoops-2026-01HXK5MWDJ6QZXN8VBCD9EF2GH.html",
            "author": {
                "id": 1,
                "username": "admin",
                "display_name": "Site Admin"
            },
            "categories": [
                {
                    "id": "01HXK5NRKS2QZXN8VBCD9EF2GH",
                    "name": "Tutorials",
                    "slug": "tutorials"
                }
            ],
            "tags": ["xoops", "tutorial", "getting-started"],
            "published_at": "2026-01-15T10:30:00Z",
            "featured_image": "https://your-site.com/uploads/goldstandard/featured-image.jpg"
        },
        "previous_status": "draft"
    },
    "metadata": {
        "module": "goldstandard",
        "version": "1.0",
        "site_url": "https://your-site.com"
    }
}

Article Updated Event

{
    "id": "evt_01HXK5RWGM3QZXN8VBCD9EF2GH",
    "type": "article.updated",
    "created_at": "2026-01-15T11:00:00Z",
    "data": {
        "article": {
            "id": "01HXK5MWDJ6QZXN8VBCD9EF2GH",
            "title": "Getting Started with XOOPS 2026 (Updated)",
            "url": "https://your-site.com/modules/goldstandard/article/..."
        },
        "changes": {
            "title": {
                "old": "Getting Started with XOOPS 2026",
                "new": "Getting Started with XOOPS 2026 (Updated)"
            },
            "content": {
                "changed": true
            }
        },
        "updated_by": {
            "id": 1,
            "username": "admin"
        }
    }
}

Comment Created Event

{
    "id": "evt_01HXK5SWGM3QZXN8VBCD9EF2GH",
    "type": "comment.created",
    "created_at": "2026-01-15T12:00:00Z",
    "data": {
        "comment": {
            "id": "01HXK5TWGM3QZXN8VBCD9EF2GH",
            "content": "Great article! Very helpful.",
            "author": {
                "id": 5,
                "username": "johndoe",
                "display_name": "John Doe"
            },
            "status": "pending",
            "created_at": "2026-01-15T12:00:00Z"
        },
        "article": {
            "id": "01HXK5MWDJ6QZXN8VBCD9EF2GH",
            "title": "Getting Started with XOOPS 2026",
            "url": "https://your-site.com/modules/goldstandard/article/..."
        },
        "parent_comment": null
    }
}

Webhook Security

Signature Verification

All webhook requests include a signature header for verification:

X-GoldStandard-Signature: sha256=5d4e32a1f2b3c4d5e6f7a8b9c0d1e2f3...

Verifying Signatures (PHP)

function verifyWebhookSignature(
    string $payload,
    string $signature,
    string $secret
): bool {
    $expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
    return hash_equals($expected, $signature);
}

// In your webhook handler
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_GOLDSTANDARD_SIGNATURE'] ?? '';
$secret = 'your-webhook-secret';

if (!verifyWebhookSignature($payload, $signature, $secret)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($payload, true);
// Process event...

Verifying Signatures (Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
    const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');
    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signature)
    );
}

Retry Policy

Failed webhook deliveries are retried with exponential backoff:

Attempt Delay
1 Immediate
2 1 minute
3 5 minutes
4 30 minutes
5 2 hours
6 8 hours
7 24 hours

After 7 failed attempts, the webhook is marked as failed and an admin notification is sent.

Webhook Responses

Your endpoint should respond with a 2xx status code to indicate successful receipt:

{
    "received": true
}

Responses with 4xx or 5xx status codes trigger retries.

Event Subscription API

List Webhooks

GET /api/v1/webhooks
Authorization: Bearer {token}

Get Webhook

GET /api/v1/webhooks/{webhook_id}
Authorization: Bearer {token}

Update Webhook

PATCH /api/v1/webhooks/{webhook_id}
Content-Type: application/json
Authorization: Bearer {token}

{
    "events": ["article.published", "article.updated"],
    "active": true
}

Delete Webhook

DELETE /api/v1/webhooks/{webhook_id}
Authorization: Bearer {token}

List Webhook Deliveries

GET /api/v1/webhooks/{webhook_id}/deliveries
Authorization: Bearer {token}

Retry Failed Delivery

POST /api/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retry
Authorization: Bearer {token}

PHP Event Dispatching

Dispatching Webhook Events

namespace Xoops\Modules\GoldStandard\Infrastructure\Webhook;

class WebhookDispatcher
{
    public function __construct(
        private readonly WebhookRepository $webhooks,
        private readonly HttpClient $client,
        private readonly QueueInterface $queue
    ) {}

    public function dispatch(string $eventType, array $data): void
    {
        $webhooks = $this->webhooks->findByEvent($eventType);

        foreach ($webhooks as $webhook) {
            if (!$webhook->isActive()) {
                continue;
            }

            $payload = $this->buildPayload($eventType, $data);

            // Queue for async delivery
            $this->queue->push(new DeliverWebhookJob(
                webhookId: $webhook->getId(),
                payload: $payload
            ));
        }
    }

    private function buildPayload(string $eventType, array $data): array
    {
        return [
            'id' => 'evt_' . Ulid::generate(),
            'type' => $eventType,
            'created_at' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
            'data' => $data,
            'metadata' => [
                'module' => 'goldstandard',
                'version' => '1.0',
                'site_url' => XOOPS_URL
            ]
        ];
    }
}

Webhook Delivery Job

namespace Xoops\Modules\GoldStandard\Infrastructure\Webhook;

class DeliverWebhookJob
{
    public function __construct(
        public readonly string $webhookId,
        public readonly array $payload,
        public readonly int $attempt = 1
    ) {}

    public function handle(
        WebhookRepository $webhooks,
        HttpClient $client,
        QueueInterface $queue
    ): void {
        $webhook = $webhooks->findById($this->webhookId);

        if (!$webhook) {
            return;
        }

        $signature = $this->generateSignature(
            json_encode($this->payload),
            $webhook->getSecret()
        );

        try {
            $response = $client->post($webhook->getUrl(), [
                'json' => $this->payload,
                'headers' => [
                    'Content-Type' => 'application/json',
                    'X-GoldStandard-Signature' => $signature,
                    'X-GoldStandard-Event' => $this->payload['type'],
                    'X-GoldStandard-Delivery' => $this->payload['id']
                ],
                'timeout' => 30
            ]);

            $this->logDelivery($webhook, $response, 'success');

        } catch (\Exception $e) {
            $this->logDelivery($webhook, null, 'failed', $e->getMessage());

            if ($this->attempt < 7) {
                $delay = $this->getRetryDelay($this->attempt);

                $queue->later($delay, new self(
                    webhookId: $this->webhookId,
                    payload: $this->payload,
                    attempt: $this->attempt + 1
                ));
            }
        }
    }

    private function generateSignature(string $payload, string $secret): string
    {
        return 'sha256=' . hash_hmac('sha256', $payload, $secret);
    }

    private function getRetryDelay(int $attempt): int
    {
        return match($attempt) {
            1 => 60,        // 1 minute
            2 => 300,       // 5 minutes
            3 => 1800,      // 30 minutes
            4 => 7200,      // 2 hours
            5 => 28800,     // 8 hours
            6 => 86400,     // 24 hours
            default => 86400
        };
    }
}

Testing Webhooks

Test Endpoint

Use the test endpoint to verify your webhook configuration:

POST /api/v1/webhooks/{webhook_id}/test
Authorization: Bearer {token}

This sends a test event to your webhook URL:

{
    "id": "evt_test_01HXK5UWGM3QZXN8VBCD9EF2GH",
    "type": "webhook.test",
    "created_at": "2026-01-15T10:30:00Z",
    "data": {
        "message": "This is a test webhook delivery"
    },
    "metadata": {
        "module": "goldstandard",
        "version": "1.0",
        "site_url": "https://your-site.com"
    }
}

Best Practices

  1. Respond Quickly: Return 2xx within 30 seconds
  2. Process Asynchronously: Queue webhook data for processing
  3. Verify Signatures: Always verify webhook signatures
  4. Handle Duplicates: Events may be delivered more than once
  5. Log Everything: Keep records for debugging
  6. Monitor Failures: Set up alerts for failed deliveries