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
| Mode | Header | Uso | Rate-limit default |
|---|---|---|---|
HMAC | X-Flowforge-Signature: sha256=<hex> | Webhook (Stripe-style) | 100/min IP |
Bearer Invoke | Authorization: Bearer inv_<ws>_<secret> | REST invoke | 30/min token |
Bearer MCP | Authorization: Bearer mcp_<ws>_<secret> | MCP JSON-RPC 2.0 | 60/min token |
Form public | — | POST form HTML | configurable 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>7. Errori standardizzati
Tutti gli endpoint ritornano: { ok: false, error: { code, message, hint? } }.
| Status | Code | Quando |
|---|---|---|
400 | INVALID_INPUT | Zod validation fail |
401 | UNAUTHORIZED | Token mancante/invalido |
403 | FORBIDDEN | Quota/feature gate (es. Free max 5 workflow) |
404 | NOT_FOUND | Workflow/token inesistente |
408 | TIMEOUT | Workflow execution timeout |
409 | IDEMPOTENT_CONFLICT | Idempotency-Key con body diverso |
429 | RATE_LIMITED | Header Retry-After: <sec> |
503 | WAKING | Container cold-start (retry con backoff) |
8. Cold-start handling
Container tenant in pausa idle (Free: 10 min). Primo request:
- nginx serve
@wakingpage (HTML 503 + auto-retry) - portal
/api/v1/internal/wake-by-slugdespausa container Docker - 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.
Domande?
Scrivi a [email protected] per integrazione enterprise o supporto stack specifico. Tempo risposta < 4h business hours (CEST).
