Skip to main content

Webhooks

Two distinct flows:

  1. Outbound webhooks — RuleForge sends events to a URL of yours when something happens (validation finishes, version publishes, etc.). Endpoints /platform/organizations/{id}/webhooks/*.
  2. Inbound webhooks — Git providers (GitHub, GitLab, Gitea) send push events to RuleForge, which triggers binding auto-import. Endpoints /webhooks/git/*.

Outbound — endpoint table

MethodPathPermission
POST/platform/organizations/{organization_id}/webhooksintegration:manage
GET/platform/organizations/{organization_id}/webhooksSession
GET/platform/organizations/{organization_id}/webhooks/{webhook_id}Session
PATCH/platform/organizations/{organization_id}/webhooks/{webhook_id}integration:manage
DELETE/platform/organizations/{organization_id}/webhooks/{webhook_id}integration:manage
GET/platform/organizations/{organization_id}/webhooks/{webhook_id}/deliveriesSession
POST/platform/organizations/{organization_id}/webhooks/{webhook_id}/testintegration:manage

Inbound — endpoint table

MethodPathAuth
POST/webhooks/git/githubHMAC SHA-256
POST/webhooks/git/gitlabSecret token
POST/webhooks/git/giteaHMAC SHA-256

Outbound

POST /.../webhooks

Registers an outbound webhook.

Request body (WebhookCreateRequest):

{
"url": "https://webhooks.acme.com/ruleforge",
"events": [
"validation.completed",
"regression.failed",
"version.published"
],
"secret": "a-secret-string-with-16-plus-chars",
"description": "SOC team channel"
}
  • url — 8–2048 chars, must be http:// or https://. By default private/loopback IPs are blocked (operator-controlled via NATIVE_WAZUH_WEBHOOK_BLOCK_PRIVATE_NETWORKS).
  • events[] — at least 1 event, all must be valid values. See available events.
  • secret — 16–256 chars. Used to sign the payload with HMAC SHA-256 in the X-RuleForge-Signature header.

Response 201 (WebhookRecord).

Supported events

validation.completed
logtest.completed
regression.completed
regression.passed
regression.failed
review.created
review.approved
review.changes_requested
review.rejected
version.created
version.published
publish.failed
feedback.created
api_key.created
api_key.revoked
api.quota.warning

Detailed description in Webhook events.

Delivered payload format

Each delivery is a POST to your endpoint:

POST /ruleforge HTTP/1.1
Host: webhooks.acme.com
Content-Type: application/json
X-RuleForge-Event: validation.completed
X-RuleForge-Delivery: 8e1f2c3a…
X-RuleForge-Signature: sha256=abc123…
User-Agent: RuleForge-Webhooks/1.0

{
"event": "validation.completed",
"delivery_id": "8e1f2c3a…",
"organization_id": "org_...",
"resource_type": "analysis",
"resource_id": null,
"payload": {
"ok": true,
"diagnostic_count": 0,
"total_rules": 8621,
"total_decoders": 623
},
"occurred_at": "2026-04-24T15:30:00Z"
}

Signature verification (Python example):

import hmac, hashlib

def verify(body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)

Response expectation

  • Your endpoint must reply 2xx within 10 s (NATIVE_WAZUH_WEBHOOK_TIMEOUT_SECONDS, default 10).
  • Any 4xx/5xx is considered failure → RuleForge retries with exponential backoff.
  • If your service goes down, failure_count rises; after repeated failures the webhook may be marked is_active=false by the operator.

GET /.../webhooks

Lists webhooks.

Response 200: array of WebhookRecord:

{
"id": "wh_...",
"organization_id": "org_...",
"url": "https://webhooks.acme.com/ruleforge",
"target_host": "webhooks.acme.com",
"destination_type": "custom",
"events": ["validation.completed"],
"description": "SOC channel",
"is_active": true,
"delivery_count": 1423,
"failure_count": 12,
"last_delivery_at": "2026-04-24T15:30:00Z",
"secret_masked": "abc***xyz",
"created_at": "...",
"updated_at": "..."
}

PATCH /.../webhooks/{id}

Partial update. To swap the secret, send secret in the body; without it the current one is preserved.

DELETE /.../webhooks/{id}

Removes the webhook. Response 204.

GET /.../webhooks/{id}/deliveries

Delivery history (last N).

Response 200 (WebhookDeliveryRecord[]):

[
{
"id": "del_...",
"webhook_id": "wh_...",
"event_type": "validation.completed",
"status": "delivered",
"response_status": 200,
"duration_ms": 412.3,
"response_snippet": "{\"ok\":true}",
"error_message": null,
"attempt_count": 1,
"next_retry_at": null,
"delivered_at": "2026-04-24T15:30:01Z",
"created_at": "2026-04-24T15:30:00Z"
},
{
"id": "del_...",
"webhook_id": "wh_...",
"event_type": "version.published",
"status": "failed",
"response_status": 502,
"duration_ms": 8932.1,
"error_message": "Bad Gateway",
"attempt_count": 3,
"next_retry_at": "2026-04-24T15:40:00Z",
"created_at": "2026-04-24T15:20:00Z"
}
]

POST /.../webhooks/{id}/test

Triggers a test delivery with a synthetic payload. Useful to validate your endpoint without waiting for a real event.

Request body (optional): { "event_type": "validation.completed" }. Default is validation.completed.

Response 200:

{
"delivered": true,
"response_status": 200,
"duration_ms": 412.3
}

Inbound (Git)

Endpoints that receive Git-provider push events. They trigger auto-import for every binding pointing to repository@branch.

Do not call these manually — configure them as webhooks on the corresponding Git platforms.

POST /webhooks/git/github

Expected headers:

Content-Type: application/json
X-GitHub-Event: push
X-GitHub-Delivery: <uuid>
X-Hub-Signature-256: sha256=<hmac>

Payload: native GitHub Push Event format. Fields read:

  • ref (must start with refs/heads/).
  • repository.full_name.
  • after (commit SHA).
  • pusher.name.

Verification: HMAC SHA-256 of the body with the secret configured on the integration. The secret is generated in the UI when the webhook is created on GitHub.

Response 200:

{ "status": "ok", "bindings_triggered": 2 }

Response 401: invalid signature.

POST /webhooks/git/gitlab

Expected headers:

Content-Type: application/json
X-Gitlab-Event: Push Hook
X-Gitlab-Token: <secret>

Payload: GitLab Push Hook. Fields read: ref, project.path_with_namespace, checkout_sha.

Verification: comparison of X-Gitlab-Token with the configured secret.

POST /webhooks/git/gitea

Headers: similar to GitHub (X-Gitea-Event, X-Gitea-Signature).

Verification: HMAC SHA-256 of the body.

How RuleForge uses inbound webhooks

  1. Receives the push event.
  2. Verifies signature/token.
  3. Extracts (provider, repository, branch, commit_sha).
  4. Finds all active bindings with sync_on_webhook=true that match.
  5. Queues a git_sync job for each binding.
  6. Returns 200 to the provider (processing is asynchronous).

Status of the triggered syncs is available at GET /.../git-bindings/{id}/runs.

Shared structures

WebhookRecord

Shown above.

WebhookDeliveryRecord

Shown above. statusfailed.