Skip to content

Webhooks API

Moxxy supports inbound webhooks for integrating external services with agents. Webhooks are registered via the webhook.register primitive and verified using HMAC signatures.

How Webhooks Work

  1. An agent registers a webhook using the webhook.register primitive during a run
  2. Moxxy generates a unique token and HMAC secret for the webhook
  3. External services send POST requests to /v1/hooks/{token}
  4. Moxxy verifies the HMAC signature and delivers the payload to the agent as a new run
External Service                  Moxxy Gateway                    Agent
      |                                |                              |
      |  POST /v1/hooks/{token}        |                              |
      |  X-Signature: hmac-sha256=...  |                              |
      |------------------------------->|                              |
      |                                |  verify HMAC signature       |
      |                                |  record in webhook_deliveries|
      |                                |  start run with payload      |
      |                                |----------------------------->|
      |         200 OK                 |                              |
      |<-------------------------------|                              |

Receive Inbound Webhook

POST /v1/hooks/{token}

This endpoint receives webhook payloads from external services. It does not require Bearer token authentication -- instead, the payload is verified via HMAC signature.

Headers

HeaderRequiredDescription
X-SignatureYesHMAC-SHA256 signature of the request body
Content-TypeYesapplication/json

Request Body

Any valid JSON payload. The entire body is delivered to the agent.

Example

bash
# Compute HMAC signature
SIGNATURE=$(echo -n '{"event":"push","repo":"user/project"}' | \
  openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')

curl -X POST http://127.0.0.1:3000/v1/hooks/whk_abc123 \
  -H "Content-Type: application/json" \
  -H "X-Signature: sha256=$SIGNATURE" \
  -d '{"event":"push","repo":"user/project"}'

Response (200)

json
{
  "status": "accepted",
  "delivery_id": "del_xyz789"
}

Error Responses

StatusReason
400Missing or malformed body
401Invalid or missing HMAC signature
404Unknown webhook token

List Agent Webhooks

GET /v1/agents/{name}/webhooks

Returns all webhooks registered by an agent.

Example

bash
curl http://127.0.0.1:3000/v1/agents/devops/webhooks

Response (200)

json
[
  {
    "slug": "github-push",
    "token": "whk_abc123",
    "created_at": "2025-03-15T10:00:00Z",
    "last_delivery": "2025-03-15T12:00:00Z",
    "delivery_count": 42
  },
  {
    "slug": "alerts",
    "token": "whk_def456",
    "created_at": "2025-03-16T09:00:00Z",
    "last_delivery": null,
    "delivery_count": 0
  }
]

Delete Webhook

DELETE /v1/agents/{name}/webhooks/{slug}

Removes a webhook registration. The token is invalidated and future requests to the hook URL will return 404.

Example

bash
curl -X DELETE http://127.0.0.1:3000/v1/agents/devops/webhooks/github-push

Response (204)

No content.

Webhook Registration

Webhooks are registered by the agent itself using the webhook.register primitive during a run. The agent specifies a slug (human-readable identifier) and receives the token and HMAC secret.

webhook.register("github-push")

The primitive returns:

  • token -- The unique URL path component (used in /v1/hooks/{token})
  • secret -- The HMAC secret for signature verification

The agent can share the hook URL and secret with the external service.

Token Rotation

Webhook tokens can be rotated using the webhook.rotate primitive:

webhook.rotate("github-push")

This generates a new token and HMAC secret. The old token is immediately invalidated. The agent receives the new credentials and can update the external service.

Delivery Tracking

All webhook deliveries are recorded in the webhook_deliveries table with the following information:

FieldDescription
delivery_idUnique delivery identifier
webhook_slugThe webhook that received the delivery
source_ipIP address of the sender
signature_validWhether the HMAC signature was valid
run_idThe run ID created to process the delivery (if accepted)
status_codeHTTP status code returned to the sender
received_atTimestamp of the delivery

This provides a full audit trail for debugging and security monitoring.

HMAC Signature Verification

External services must sign the request body using HMAC-SHA256 with the webhook secret:

  1. Compute HMAC-SHA256(secret, request_body) to get the signature
  2. Send the signature in the X-Signature header as sha256={hex_digest}
  3. Moxxy recomputes the HMAC and compares it to the provided signature
  4. If the signatures do not match, the request is rejected with 401

Verification Examples

Node.js:

javascript
const crypto = require('crypto');

function signPayload(secret, body) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(body);
  return 'sha256=' + hmac.digest('hex');
}

Python:

python
import hmac
import hashlib

def sign_payload(secret: str, body: bytes) -> str:
    signature = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return f"sha256={signature}"

Open source · Self-hosted · Data sovereign