Skip to content

Webhooks (Beta)

Beta

Webhooks are currently in beta. The API and event types described on this page may change. A management UI is coming in a future release.

Webhooks let the Manifest Platform push real-time event notifications to your application. When an event occurs (e.g., a workflow completes, a deployment succeeds, an agent execution finishes), the platform sends an HTTP POST request to your configured URL with a JSON payload describing the event.


How Webhooks Work

sequenceDiagram
    participant Platform as Manifest Platform
    participant Queue as Event Queue
    participant Target as Your Endpoint

    Platform->>Queue: Event occurs (workflow.completed)
    Queue->>Target: POST /your-webhook-url
    Target-->>Queue: 200 OK
    Note over Queue: Event delivered successfully

    Queue->>Target: POST /your-webhook-url
    Target-->>Queue: 500 Error
    Note over Queue: Retry with backoff
    Queue->>Target: POST /your-webhook-url (retry 1)
    Target-->>Queue: 200 OK

Managing Webhooks

Creating a Webhook

Webhooks are managed via the API using your organization API key.

curl -X POST \
  -H "X-API-Key: fl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/flow-events",
    "secret": "whsec_your_signing_secret",
    "events": [
      "workflow.completed",
      "workflow.failed",
      "deployment.succeeded",
      "deployment.failed"
    ],
    "tenant_id": null
  }' \
  https://api.flow.marut.cloud/api/v1/webhooks/subscriptions

Listing Webhooks

curl -H "X-API-Key: fl_your_api_key" \
     https://api.flow.marut.cloud/api/v1/webhooks/subscriptions

Updating a Webhook

To change events or URL, delete and recreate the subscription.

Deleting a Webhook

curl -X DELETE \
  -H "X-API-Key: fl_your_api_key" \
  https://api.flow.marut.cloud/api/v1/webhooks/subscriptions/{subscription_id}

Event Types

Subscribe to the events relevant to your use case. Each event type follows the pattern resource.action.

Workflow Events

Event Triggered When
workflow.completed A workflow execution finishes successfully
workflow.failed A workflow execution fails
workflow.started A workflow execution begins
workflow.step.completed An individual workflow step completes
workflow.step.failed An individual workflow step fails

Agent Events

Event Triggered When
agent.execution.completed An agent execution finishes
agent.execution.failed An agent execution fails
agent.execution.started An agent execution begins

Deployment Events

Event Triggered When
deployment.succeeded A deployment completes successfully
deployment.failed A deployment fails
deployment.promoted A deployment is promoted to a new ring
deployment.rolled_back A deployment is rolled back

Hosted Service Events

Event Triggered When
service.published A hosted service version is published
service.deployed A persistent service is deployed
service.job.completed An async job completes
service.job.failed An async job fails
service.job.failed An async job exhausts all retry attempts

Organization Events

Event Triggered When
user.invited A new user is invited to the organization
user.removed A user is removed from the organization
api_key.created A new API key is created
api_key.revoked An API key is revoked

Use wildcards

Subscribe to workflow.* to receive all workflow events, or * to receive every event type. Wildcards apply only at the resource level.


Payload Format

Every webhook delivery includes a JSON payload with a consistent structure.

Common Envelope

{
  "id": "evt_550e8400-e29b-41d4-a716-446655440000",
  "type": "workflow.completed",
  "timestamp": "2026-03-21T14:30:00.000Z",
  "organization_id": "org-uuid",
  "workspace_id": "ws-uuid",
  "data": {
    // Event-specific payload (see below)
  }
}
Field Type Description
id string Unique event ID (for deduplication)
type string Event type (e.g., workflow.completed)
timestamp string ISO 8601 timestamp of when the event occurred
organization_id string Organization that owns the resource
workspace_id string Workspace context
data object Event-specific details

Example: workflow.completed

{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "workflow.completed",
  "timestamp": "2026-03-21T14:30:00.000Z",
  "organization_id": "org-uuid",
  "workspace_id": "ws-uuid",
  "data": {
    "workflow_id": "wf-uuid",
    "workflow_name": "Document Ingestion Pipeline",
    "execution_id": "exec-uuid",
    "duration_ms": 12450,
    "steps_completed": 5,
    "output": {
      "documents_processed": 42,
      "errors": 0
    }
  }
}

Example: deployment.failed

{
  "id": "evt_f1e2d3c4-b5a6-7890-fedc-ba0987654321",
  "type": "deployment.failed",
  "timestamp": "2026-03-21T15:00:00.000Z",
  "organization_id": "org-uuid",
  "workspace_id": "ws-uuid",
  "data": {
    "deployment_id": "deploy-uuid",
    "component_id": "comp-uuid",
    "component_name": "Invoice Classifier Agent",
    "ring": "staging",
    "error": "Health check failed after 3 attempts",
    "revision": "v2.1.0"
  }
}

Webhook Configuration

Signing Secret

Every webhook delivery includes a signature in the X-Flow-Signature-256 header. Use this to verify that the payload was sent by Manifest Platform and was not tampered with.

The signature is an HMAC-SHA256 hex digest of the raw request body, using your webhook secret as the key.

Verifying Signatures

import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    """Verify a Manifest Platform webhook signature."""
    expected = hmac.new(
        secret.encode("utf-8"),
        payload,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler:
# payload = request.body (raw bytes)
# signature = request.headers["X-Flow-Signature-256"]
# secret = "whsec_your_signing_secret"
const crypto = require("crypto");

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf-8")
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature)
  );
}
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func verifyWebhook(payload []byte, signature string, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}

Always verify signatures

Without signature verification, an attacker could send forged events to your endpoint. Always verify the X-Flow-Signature-256 header before processing a webhook payload.


Retry Logic

If your endpoint returns a non-2xx status code or times out, the platform retries the delivery with exponential backoff.

Attempt Delay After Failure
1st retry 10 seconds
2nd retry 1 minute
3rd retry 10 minutes
4th retry 1 hour
5th retry 6 hours

After 5 failed retries, the event is marked as failed and no further attempts are made.

Delivery Requirements

Your endpoint must meet these requirements for reliable delivery:

  • Return 2xx within 10 seconds. If processing takes longer, accept the webhook, queue the work, and return 200 OK immediately.
  • Be idempotent. Use the id field to deduplicate events. The platform may deliver the same event more than once due to retries.
  • Accept POST requests with Content-Type: application/json.

Debugging Webhooks

Delivery Logs

View recent webhook deliveries via the API:

curl -H "X-API-Key: fl_your_api_key" \
     "https://api.flow.marut.cloud/api/v1/webhooks/deliveries?subscription_id={subscription_id}"

Each delivery record includes the event type, response status code, duration, and timestamp.

Testing with a Ping

When a ping is sent, your endpoint receives a ping event with a minimal payload:

{
  "id": "evt_ping_abc123",
  "type": "ping",
  "timestamp": "2026-03-21T14:30:00.000Z",
  "organization_id": "org-uuid",
  "workspace_id": "ws-uuid",
  "data": {}
}

Troubleshooting

Webhooks are not being delivered
  • Check that the URL is reachable from the internet (HTTPS required).
  • Review the delivery logs for error details.
  • Send a test event to verify connectivity.
Receiving duplicate events
  • This is expected behavior during retries. Use the id field on each event to deduplicate in your handler.
  • If duplicates appear outside of retry windows, check that you do not have multiple webhooks configured for the same events.
Signature verification is failing
  • Verify you are using the raw request body bytes (not a parsed/re-serialized JSON string).
  • Confirm the secret matches exactly what was configured when the webhook was created.
  • Check for any middleware that modifies the request body before your verification logic runs.

Next Steps