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
- An agent registers a webhook using the
webhook.registerprimitive during a run - Moxxy generates a unique token and HMAC secret for the webhook
- External services send POST requests to
/v1/hooks/{token} - 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
| Header | Required | Description |
|---|---|---|
X-Signature | Yes | HMAC-SHA256 signature of the request body |
Content-Type | Yes | application/json |
Request Body
Any valid JSON payload. The entire body is delivered to the agent.
Example
# 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)
{
"status": "accepted",
"delivery_id": "del_xyz789"
}Error Responses
| Status | Reason |
|---|---|
| 400 | Missing or malformed body |
| 401 | Invalid or missing HMAC signature |
| 404 | Unknown webhook token |
List Agent Webhooks
GET /v1/agents/{name}/webhooksReturns all webhooks registered by an agent.
Example
curl http://127.0.0.1:3000/v1/agents/devops/webhooksResponse (200)
[
{
"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
curl -X DELETE http://127.0.0.1:3000/v1/agents/devops/webhooks/github-pushResponse (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:
| Field | Description |
|---|---|
delivery_id | Unique delivery identifier |
webhook_slug | The webhook that received the delivery |
source_ip | IP address of the sender |
signature_valid | Whether the HMAC signature was valid |
run_id | The run ID created to process the delivery (if accepted) |
status_code | HTTP status code returned to the sender |
received_at | Timestamp 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:
- Compute
HMAC-SHA256(secret, request_body)to get the signature - Send the signature in the
X-Signatureheader assha256={hex_digest} - Moxxy recomputes the HMAC and compares it to the provided signature
- If the signatures do not match, the request is rejected with 401
Verification Examples
Node.js:
const crypto = require('crypto');
function signPayload(secret, body) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(body);
return 'sha256=' + hmac.digest('hex');
}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}"