External API Reference

Integra FlowForge da qualsiasi stack

REST API per webhooks, esecuzione workflow, MCP per LLM agentici, form pubblici, share link. Esempi pronti in 7 linguaggi.

1. Autenticazione + rate-limit

ModeHeaderUsoRate-limit default
HMACX-Flowforge-Signature: sha256=<hex>Webhook (Stripe-style)100/min IP
Bearer InvokeAuthorization: Bearer inv_<ws>_<secret>REST invoke30/min token
Bearer MCPAuthorization: Bearer mcp_<ws>_<secret>MCP JSON-RPC 2.060/min token
Form publicPOST form HTMLconfigurable per form

Rate-limit override via piano (PRO/TEAM/ENT moltiplicatori in flowforge.plans). Header Retry-After: <sec> su 429.

2. Webhook triggers

Endpoint: POST /webhooks/c/<tenantSlug>/<workflowToken>.
Risposta default: 202 { runId }. Custom response (status/body/headers) tramite nodo action_webhook_respond nel workflow.

HMAC verification

signature = hex(hmac_sha256(workflow_secret, rawBody)) → compare timing-safe contro header X-Flowforge-Signature: sha256=<signature>.

curl -X POST https://<slug>.app.automazionezeli.com/webhooks/c/<tenant>/<token> \
  -H "Content-Type: application/json" \
  -H "X-Flowforge-Signature: sha256=<hmac>" \
  -d '{"id":"evt_xxx","type":"payment.succeeded","data":{...}}'
// Node.js (built-in fetch, Node 18+)
import crypto from 'node:crypto';

const body = JSON.stringify({ id: 'evt_xxx', type: 'payment.succeeded', data: { amount: 4200 } });
const signature = crypto.createHmac('sha256', process.env.FLOWFORGE_WEBHOOK_SECRET)
  .update(body).digest('hex');

const res = await fetch('https://acme.app.automazionezeli.com/webhooks/c/acme/wf_a8b3c2', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Flowforge-Signature': `sha256=${signature}`,
  },
  body,
});

console.log(res.status, await res.json()); // → 202 { runId: "..." }
# Python 3.9+ (httpx o requests)
import hmac, hashlib, json, httpx, os

body = json.dumps({"id": "evt_xxx", "type": "payment.succeeded", "data": {"amount": 4200}})
signature = hmac.new(
    os.environ["FLOWFORGE_WEBHOOK_SECRET"].encode(),
    body.encode(),
    hashlib.sha256,
).hexdigest()

r = httpx.post(
    "https://acme.app.automazionezeli.com/webhooks/c/acme/wf_a8b3c2",
    content=body,
    headers={
        "Content-Type": "application/json",
        "X-Flowforge-Signature": f"sha256={signature}",
    },
    timeout=15.0,
)
print(r.status_code, r.json())  # → 202 { runId: "..." }
// .NET 8+ (HttpClient)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;

var body = """{"id":"evt_xxx","type":"payment.succeeded","data":{"amount":4200}}""";
var secret = Environment.GetEnvironmentVariable("FLOWFORGE_WEBHOOK_SECRET")!;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var sig = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(body))).ToLower();

var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post,
    "https://acme.app.automazionezeli.com/webhooks/c/acme/wf_a8b3c2");
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
req.Headers.Add("X-Flowforge-Signature", $"sha256={sig}");

var res = await client.SendAsync(req);
Console.WriteLine($"{(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");
<?php
// PHP 8.1+ (cURL)
$body = json_encode([
    'id' => 'evt_xxx', 'type' => 'payment.succeeded',
    'data' => ['amount' => 4200],
]);
$signature = hash_hmac('sha256', $body, getenv('FLOWFORGE_WEBHOOK_SECRET'));

$ch = curl_init('https://acme.app.automazionezeli.com/webhooks/c/acme/wf_a8b3c2');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        "X-Flowforge-Signature: sha256={$signature}",
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $response, "\n";
// Java 11+ (HttpClient + Mac HMAC)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;

String body = "{\"id\":\"evt_xxx\",\"type\":\"payment.succeeded\",\"data\":{\"amount\":4200}}";
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(System.getenv("FLOWFORGE_WEBHOOK_SECRET").getBytes(), "HmacSHA256"));
byte[] sigBytes = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : sigBytes) sb.append(String.format("%02x", b));

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://acme.app.automazionezeli.com/webhooks/c/acme/wf_a8b3c2"))
    .header("Content-Type", "application/json")
    .header("X-Flowforge-Signature", "sha256=" + sb)
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());
// Go 1.21+
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    body := []byte(`{"id":"evt_xxx","type":"payment.succeeded","data":{"amount":4200}}`)
    h := hmac.New(sha256.New, []byte(os.Getenv("FLOWFORGE_WEBHOOK_SECRET")))
    h.Write(body)
    signature := hex.EncodeToString(h.Sum(nil))

    req, _ := http.NewRequest("POST",
        "https://acme.app.automazionezeli.com/webhooks/c/acme/wf_a8b3c2",
        bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-Flowforge-Signature", "sha256="+signature)

    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    respBody, _ := io.ReadAll(res.Body)
    fmt.Printf("%d %s\n", res.StatusCode, respBody)
}

3. Invoke REST (sincrono)

Esegui un workflow come "function-as-a-service" dall'esterno. Risposta sincrona con runId + output + durationMs, oppure 408 se timeout.

Idempotency-Key supportato: stesso valore entro 24h → ritorna cached del primo invoke (anti-double-submit).

curl -X POST https://acme.app.automazionezeli.com/invoke/process-order \
  -H "Authorization: Bearer inv_<workspaceId>_<secret>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"input":{"orderId":12345,"amount":99.90},"timeoutMs":15000}'
const res = await fetch('https://acme.app.automazionezeli.com/invoke/process-order', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.FLOWFORGE_INVOKE_TOKEN}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({ input: { orderId: 12345, amount: 99.9 }, timeoutMs: 15000 }),
});
const { runId, output, durationMs } = await res.json();
console.log(runId, output);
import httpx, uuid, os

r = httpx.post(
    "https://acme.app.automazionezeli.com/invoke/process-order",
    headers={
        "Authorization": f"Bearer {os.environ['FLOWFORGE_INVOKE_TOKEN']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"input": {"orderId": 12345, "amount": 99.90}, "timeoutMs": 15000},
    timeout=30.0,
)
data = r.json()
print(data["runId"], data["output"])
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("FLOWFORGE_INVOKE_TOKEN"));

var body = JsonSerializer.Serialize(new {
    input = new { orderId = 12345, amount = 99.90 },
    timeoutMs = 15000,
});
var req = new HttpRequestMessage(HttpMethod.Post,
    "https://acme.app.automazionezeli.com/invoke/process-order");
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
req.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());

var res = await client.SendAsync(req);
var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
Console.WriteLine(doc.RootElement.GetProperty("runId"));
<?php
$payload = json_encode([
    'input' => ['orderId' => 12345, 'amount' => 99.90],
    'timeoutMs' => 15000,
]);
$ch = curl_init('https://acme.app.automazionezeli.com/invoke/process-order');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('FLOWFORGE_INVOKE_TOKEN'),
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
echo $response['runId'] ?? 'ERR';
var req = HttpRequest.newBuilder()
    .uri(URI.create("https://acme.app.automazionezeli.com/invoke/process-order"))
    .header("Authorization", "Bearer " + System.getenv("FLOWFORGE_INVOKE_TOKEN"))
    .header("Content-Type", "application/json")
    .header("Idempotency-Key", UUID.randomUUID().toString())
    .POST(HttpRequest.BodyPublishers.ofString(
        "{\"input\":{\"orderId\":12345,\"amount\":99.90},\"timeoutMs\":15000}"))
    .build();

HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body()); // → {"runId":"...","output":{...}}
body := []byte(`{"input":{"orderId":12345,"amount":99.90},"timeoutMs":15000}`)
req, _ := http.NewRequest("POST",
    "https://acme.app.automazionezeli.com/invoke/process-order",
    bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("FLOWFORGE_INVOKE_TOKEN"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())

res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
io.Copy(os.Stdout, res.Body)

4. MCP (Model Context Protocol)

Espone i workflow del tenant come tools per LLM esterni (Claude Desktop, ChatGPT, IDE agentici). Spec: modelcontextprotocol.io.

Endpoint POST /mcp JSON-RPC 2.0 (initialize / tools/list / tools/call). 1 tool MCP = 1 workflow del tenant (slug come name).

curl -X POST https://acme.app.automazionezeli.com/mcp \
  -H "Authorization: Bearer mcp_<workspaceId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
// MCP SDK ufficiale (npm i @modelcontextprotocol/sdk)
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const transport = new StreamableHTTPClientTransport(
  new URL('https://acme.app.automazionezeli.com/mcp'),
  { headers: { Authorization: `Bearer ${process.env.FLOWFORGE_MCP_TOKEN}` } },
);
const client = new Client({ name: 'my-llm-app', version: '1.0.0' });
await client.connect(transport);

const tools = await client.listTools();
console.log(tools); // → workflow esposti come MCP tools
# MCP Python SDK
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
import os

async with streamablehttp_client(
    "https://acme.app.automazionezeli.com/mcp",
    headers={"Authorization": f"Bearer {os.environ['FLOWFORGE_MCP_TOKEN']}"},
) as (read, write, _):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await session.list_tools()
        print(tools)

5. Form pubblici

Generati da trigger_form: GET/POST /forms/<workflowId>/<publicToken> con CSP + honeypot + rate-limit per IP.

Submit: application/x-www-form-urlencoded o multipart/form-data (file upload). Redirect a success page o JSON {ok:true, runId} se Accept: application/json.

<form method="POST" action="https://acme.app.automazionezeli.com/forms/wf_42/sk_abc123">
  <input name="email" type="email" required>
  <input name="message" type="textarea">
  <input name="_honeypot" type="text" style="display:none"> <!-- anti-bot -->
  <button>Invia</button>
</form>

6. Share link read-only

GET /share/run/<shareToken> espone un workflow run o dashboard a stakeholder esterni senza account. Token con scadenza configurabile (default 7gg, max 90gg). Password opzionale (Argon2id hash). Audit log su ogni accesso.

7. Errori standardizzati

Tutti gli endpoint ritornano: { ok: false, error: { code, message, hint? } }.

StatusCodeQuando
400INVALID_INPUTZod validation fail
401UNAUTHORIZEDToken mancante/invalido
403FORBIDDENQuota/feature gate (es. Free max 5 workflow)
404NOT_FOUNDWorkflow/token inesistente
408TIMEOUTWorkflow execution timeout
409IDEMPOTENT_CONFLICTIdempotency-Key con body diverso
429RATE_LIMITEDHeader Retry-After: <sec>
503WAKINGContainer cold-start (retry con backoff)

8. Cold-start handling

Container tenant in pausa idle (Free: 10 min). Primo request:

  1. nginx serve @waking page (HTML 503 + auto-retry)
  2. portal /api/v1/internal/wake-by-slug despausa container Docker
  3. Editor SPA retry automatico (max 3, exp-backoff)

Per sistemi esterni (webhook/MCP/invoke): implementare retry 503 con backoff 2s → 5s → 10s. Tutti gli SDK ufficiali lo fanno default.

9. Postman collection + OpenAPI

Esempi pronti in formato Postman v2.1 + OpenAPI 3.1. Importa nella tua workspace per testare tutti gli endpoint con auth precompilata.

↓ Postman collection (.json)↓ OpenAPI 3.1 (.yaml)

Domande?

Scrivi a [email protected] per integrazione enterprise o supporto stack specifico. Tempo risposta < 4h business hours (CEST).