Developers

Outbound Webhooks

FinkMesh can deliver signed platform events to your HTTPS endpoints. This page covers outbound platform webhooks. Inbound workflow webhooks are separate workflow trigger URLs that receive events from your apps.

Inbound Workflow Webhooks

Your app sends an event to a workflow trigger URL. The workflow receives the payload and runs after plan, readiness, quota, and rate-limit checks.

Outbound Platform Webhooks

FinkMesh sends platform events such as run completion, failures, auto-pauses, and support tickets to your app. This surface is beta and signed by endpoint secret.

Delivery Contract

POST https://your-app.example/webhooks/finkmesh
Content-Type: application/json
User-Agent: FinkMesh-Webhooks/1.0
FinkMesh-Event-Id: evt_...
FinkMesh-Event-Type: workflow.run.completed
FinkMesh-Timestamp: 1782950000
FinkMesh-Signature: v1=...

Signature

Verify FinkMesh-Signature with HMAC-SHA256 using the endpoint secret. The signed payload is timestamp.eventId.rawBody. Reject timestamps outside a five-minute tolerance to limit replay windows. Rotate secrets from Settings or the API when a receiver changes ownership.

Verify against the exact raw body bytes received by your server. Do not parse JSON and stringify it again before verification.

Event Envelope

{
  "id": "evt_...",
  "type": "workflow.run.completed",
  "apiVersion": "2026-07-01",
  "createdAt": "2026-07-01T12:00:00.000Z",
  "workspaceId": "wsp_...",
  "data": {
    "object": {
      "runId": "run_...",
      "workflowId": "wf_...",
      "status": "success"
    }
  }
}

Treat id as the idempotency key in your receiver. Store processed event ids and ignore duplicates.

Verify Signatures In Node

import crypto from "node:crypto";

export function verifyFinkMeshWebhook({
  secret,
  timestamp,
  eventId,
  rawBody,
  signature,
}: {
  secret: string;
  timestamp: string;
  eventId: string;
  rawBody: string;
  signature: string;
}) {
  const timestampSeconds = Number(timestamp);
  if (!Number.isFinite(timestampSeconds)) return false;
  const ageSeconds = Math.abs(Date.now() / 1000 - timestampSeconds);
  if (ageSeconds > 5 * 60) return false;

  const payload = `${timestamp}.${eventId}.${rawBody}`;
  const expected = "v1=" + crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  const expectedBuffer = Buffer.from(expected);
  const actualBuffer = Buffer.from(signature);

  if (expectedBuffer.length !== actualBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(expectedBuffer, actualBuffer);
}

Events

workflow.run.completedA workflow run finished successfully.
workflow.run.failedA workflow run ended with an error.
workflow.run.blockedA workflow run was blocked by readiness, quota, or guardrails.
workflow.auto_pausedA workflow was automatically paused by an error guardrail.
connection.disconnectedA workspace connection needs attention.
support.ticket.createdA support ticket was created for the workspace.

Delivery Behavior

Timeout
Receivers have 10 seconds to respond.
Success
Any 2xx response marks the delivery succeeded.
Retries
Failed deliveries retry with approximate exponential backoff, usually about 1, 2, 4, and 8 minutes with possible jitter, then become dead after five delivery attempts.
Manual retry
Retry a failed or dead delivery with POST /api/v1/webhook-deliveries/{deliveryId}/retry.
Response previews
Response previews are truncated before storage.
URL restrictions
Receiver URLs must be public HTTPS URLs and pass SSRF checks.
Secret rotation
Rotation returns the new secret once; store it immediately.
Statuses
pending, delivering, succeeded, failed, dead, skipped.

Operations

GET /api/v1/webhook-endpointsPOST /api/v1/webhook-endpointsPATCH /api/v1/webhook-endpoints/{endpointId}DELETE /api/v1/webhook-endpoints/{endpointId}POST /api/v1/webhook-endpoints/{endpointId}/testPOST /api/v1/webhook-endpoints/{endpointId}/rotate-secretGET /api/v1/webhook-endpoints/{endpointId}/deliveriesPOST /api/v1/webhook-deliveries/{deliveryId}/retry