# Middleware Templates
These templates are copy-ready starting points for synchronous Middleware Node
destinations. They are not SDKs. Adapt the decision logic, keep the
request/response shape stable, and return fast enough for live routing.

**Middleware receivers should:**

- Treat every nested object and field as optional.
- Return JSON before `execute_timeout_ms` with at least a `250ms` internal
  safety buffer.
- Use a stable response field such as `decision` so routes can evaluate
  `body_json.decision`.
- Avoid logging the full payload because it can contain IPs, user agents, URL
  parameters, and action data.
- Keep external API client timeouts low - remember this is hot-path routing so
  design accordingly.
- Read secrets from environment variables instead of hardcoding them.

**Recommended route conditions:**

| Route | Condition |
| --- | --- |
| Approved | `body_json.decision` `is` `approved` |
| Rejected | `body_json.decision` `is` `rejected` |
| Review | `body_json.decision` `is` `review` |
| Default | No conditions; used for fallback, no-match, or delivery failure. |

## AI Prompt

Copy this prompt into your AI tool, then paste the Middleware Nodes
documentation after it if the tool needs more context.

<div className="ff-scroll-code">

```text
You are creating a synchronous FunnelFlux middleware destination.

Read the FunnelFlux middleware payload documentation first. The function receives
the FunnelFlux middleware JSON payload with schema_version, event_type,
request_id, timestamp, execute_timeout_ms, visitor, funnel, traffic_source, hit,
session, tracking_fields, url_buffer, action_data, and visitor_tags.

Requirements:
- Treat every nested object and field as optional.
- Never throw because visitor/session/action/tracking data is missing.
- Be aware of passed `execute_timeout_ms` as a cap on sensible processing time.
- Keep an internal safety buffer of at least 250 ms.
- If calling external APIs, set client timeouts to intelligently handle 
slow/unresponsive APIs, with a documented fallback decision.
- Keep payload parsing, response formatting, and decision logic separate.
- Return a stable response with decision, route, score, reason, request_id, and
  schema_version so FunnelFlux routes are easy to configure.
- Use only deterministic logic unless external API calls have strict timeouts.
- Do not log the full payload or secrets.
- Do not expose API keys in the code. Read secrets from environment variables.
- The route decision must be readable by FunnelFlux with body_json.decision or similar
- Preserve FunnelFlux field names exactly.

Implement the function for: <platform/language>.
Business rule: <describe rule>.
Allowed decisions: <list of decisions here>.
Return only the function code. Do not wrap it in Markdown.
```

</div>

## JavaScript

Works as a base for Node.js HTTP servers, serverless adapters, Cloudflare
Workers, and Google Cloud Functions. Keep the generic helpers, then replace
`decide` with your own routing logic.

<div className="ff-scroll-code">

```js
function objectValue(value) {
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}

function stringValue(value, fallback = "") {
  return typeof value === "string" ? value : fallback;
}

function numberValue(value, fallback = 0) {
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}

function mapGet(map, key, fallback = "") {
  return stringValue(objectValue(map)[key], fallback);
}

function responseFor(payload, decision, reason, score = 0) {
  return {
    decision,
    route: decision,
    score,
    reason,
    request_id: stringValue(payload.request_id),
    schema_version: stringValue(payload.schema_version, "1.0"),
  };
}

function decide(payload) {
  // Replace this function with your own deterministic routing logic.
  // Read optional values defensively:
  // const trackingValue = mapGet(payload.tracking_fields, "c1");
  // const actionValue = mapGet(payload.action_data, "lead_score");
  // const visitorTagIds = Array.isArray(payload.visitor_tags)
  //   ? payload.visitor_tags
  //   : [];
  return responseFor(
    payload,
    "review",
    "replace decide() with business logic",
    0,
  );
}

async function handleFunnelFluxMiddleware(request) {
  const startedAt = Date.now();
  let payload = {};

  try {
    payload = await request.json();
  } catch (_) {
    payload = {};
  }

  const timeoutMs = Math.max(1, numberValue(payload.execute_timeout_ms, 5000));
  const deadlineMs = startedAt + Math.max(1, timeoutMs - 250);

  let response = decide(objectValue(payload));
  if (Date.now() > deadlineMs) {
    response = responseFor(
      payload,
      "review",
      "middleware safety deadline reached",
      0,
    );
  }

  return new Response(JSON.stringify(response), {
    status: 200,
    headers: { "content-type": "application/json" },
  });
}
```

</div>

### Cloudflare Workers

Paste the JavaScript helper functions above this worker export.

```js
export default {
  async fetch(request, env, ctx) {
    if (request.method !== "POST") {
      return Response.json(
        { decision: "review", reason: "POST required" },
        { status: 200 },
      );
    }

    return handleFunnelFluxMiddleware(request);
  },
};
```

### Google Cloud Functions, Node.js

Use this with the JavaScript helper functions. Configure the function timeout
above the FunnelFlux `timeoutMs`, but keep your own external calls below the
FunnelFlux deadline.

```js
exports.funnelfluxMiddleware = async (req, res) => {
  const payload = req.body && typeof req.body === "object" ? req.body : {};
  const result = decide(payload);
  res.status(200).json(result);
};
```

## TypeScript

<div className="ff-scroll-code">

```ts
type StringMap = Record<string, string>;

interface FunnelFluxMiddlewarePayload {
  schema_version?: string;
  event_type?: string;
  request_id?: string;
  timestamp?: number;
  execute_timeout_ms?: number;
  is_action?: boolean;
  action_number?: number;
  visitor?: {
    visitor_id?: string;
    ip?: string;
    user_agent?: string;
    country_code?: string;
    country_name?: string;
    region?: string;
    city?: string;
    timezone?: string;
    language?: string;
    device_type?: string;
    browser?: string;
    os?: string;
    connection_type?: string;
  };
  funnel?: {
    funnel_id?: string;
    funnel_name?: string;
    node_id?: string;
    node_name?: string;
  };
  traffic_source?: {
    traffic_source_id?: string;
    name?: string;
  };
  hit?: Record<string, unknown>;
  session?: {
    visitor_id?: string;
    hit_id?: string;
    entrance_id?: string;
    entrance_timestamp?: number;
    hit_timestamp?: number;
    reference_hit_id?: string;
    most_recent_page_id?: string;
    referrer?: string;
  };
  tracking_fields?: StringMap;
  url_buffer?: StringMap;
  action_data?: Record<string, unknown>;
  visitor_tags?: string[];
}

interface MiddlewareDecision {
  decision: "approved" | "rejected" | "review";
  route: string;
  score: number;
  reason: string;
  request_id: string;
  schema_version: string;
}

function asString(value: unknown, fallback = ""): string {
  return typeof value === "string" ? value : fallback;
}

function asMap(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function getString(map: unknown, key: string, fallback = ""): string {
  return asString(asMap(map)[key], fallback);
}

function baseDecision(
  payload: FunnelFluxMiddlewarePayload,
  decision: MiddlewareDecision["decision"],
  score: number,
  reason: string,
): MiddlewareDecision {
  return {
    decision,
    route: decision,
    score,
    reason,
    request_id: asString(payload.request_id),
    schema_version: asString(payload.schema_version, "1.0"),
  };
}

export function decide(payload: FunnelFluxMiddlewarePayload): MiddlewareDecision {
  // Replace this function with your own deterministic routing logic.
  // Read optional values defensively:
  // const trackingValue = getString(payload.tracking_fields, "c1");
  // const actionValue = getString(payload.action_data, "lead_score");
  // const visitorTagIds = Array.isArray(payload.visitor_tags)
  //   ? payload.visitor_tags
  //   : [];
  return baseDecision(
    payload,
    "review",
    0,
    "replace decide() with business logic",
  );
}
```

</div>

## Python

Works with Google Cloud Functions or Flask-style request objects.

<div className="ff-scroll-code">

```python
import time
from typing import Any, Dict

def obj(value: Any) -> Dict[str, Any]:
    return value if isinstance(value, dict) else {}

def str_value(value: Any, fallback: str = "") -> str:
    return value if isinstance(value, str) else fallback

def num_value(value: Any, fallback: int = 0) -> int:
    return value if isinstance(value, (int, float)) else fallback

def map_get(value: Any, key: str, fallback: str = "") -> str:
    return str_value(obj(value).get(key), fallback)

def response_for(
    payload: Dict[str, Any],
    decision: str,
    reason: str,
    score: int = 0,
) -> Dict[str, Any]:
    return {
        "decision": decision,
        "route": decision,
        "score": score,
        "reason": reason,
        "request_id": str_value(payload.get("request_id")),
        "schema_version": str_value(payload.get("schema_version"), "1.0"),
    }

def decide(payload: Dict[str, Any]) -> Dict[str, Any]:
    # Replace this function with your own deterministic routing logic.
    # Read optional values defensively:
    # tracking_value = map_get(payload.get("tracking_fields"), "c1")
    # action_value = map_get(payload.get("action_data"), "lead_score")
    # visitor_tag_ids = payload.get("visitor_tags")
    # if not isinstance(visitor_tag_ids, list):
    #     visitor_tag_ids = []
    return response_for(
        payload,
        "review",
        "replace decide() with business logic",
        0,
    )

def funnelflux_middleware(request):
    started_ms = int(time.time() * 1000)
    try:
        payload = request.get_json(silent=True) or {}
    except Exception:
        payload = {}

    timeout_ms = max(1, int(num_value(payload.get("execute_timeout_ms"), 5000)))
    deadline_ms = started_ms + max(1, timeout_ms - 250)

    result = decide(obj(payload))
    if int(time.time() * 1000) > deadline_ms:
        result = response_for(
            payload,
            "review",
            "middleware safety deadline reached",
            0,
        )

    return (result, 200, {"content-type": "application/json"})
```

</div>

## Go

<div className="ff-scroll-code">

```go
package middleware

import (
	"encoding/json"
	"net/http"
	"time"
)

type Payload struct {
	SchemaVersion    string            `json:"schema_version"`
	EventType        string            `json:"event_type"`
	RequestID        string            `json:"request_id"`
	Timestamp        int64             `json:"timestamp"`
	ExecuteTimeoutMS int64             `json:"execute_timeout_ms"`
	IsAction         bool              `json:"is_action"`
	ActionNumber     int               `json:"action_number"`
	Visitor          map[string]any    `json:"visitor"`
	Funnel           map[string]any    `json:"funnel"`
	TrafficSource    map[string]any    `json:"traffic_source"`
	Hit              map[string]any    `json:"hit"`
	Session          map[string]any    `json:"session"`
	TrackingFields   map[string]string `json:"tracking_fields"`
	URLBuffer        map[string]string `json:"url_buffer"`
	ActionData       map[string]any    `json:"action_data"`
	VisitorTags      []string          `json:"visitor_tags"`
}

type Decision struct {
	Decision      string `json:"decision"`
	Route         string `json:"route"`
	Score         int    `json:"score"`
	Reason        string `json:"reason"`
	RequestID     string `json:"request_id"`
	SchemaVersion string `json:"schema_version"`
}

func Handler(w http.ResponseWriter, r *http.Request) {
	started := time.Now()
	var payload Payload
	_ = json.NewDecoder(r.Body).Decode(&payload)

	timeout := time.Duration(payload.ExecuteTimeoutMS) * time.Millisecond
	if timeout <= 0 {
		timeout = 5 * time.Second
	}
	deadline := started.Add(timeout - 250*time.Millisecond)

	result := decide(payload)
	if time.Now().After(deadline) {
		result = baseDecision(
			payload,
			"review",
			0,
			"middleware safety deadline reached",
		)
	}

	w.Header().Set("content-type", "application/json")
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(result)
}

func decide(payload Payload) Decision {
	// Replace this function with your own deterministic routing logic.
	// Read optional values defensively:
	// trackingValue := payload.TrackingFields["c1"]
	// actionValue := stringFromAny(payload.ActionData["lead_score"])
	// visitorTagIDs := payload.VisitorTags
	return baseDecision(
		payload,
		"review",
		0,
		"replace decide() with business logic",
	)
}

func baseDecision(
	payload Payload,
	decision string,
	score int,
	reason string,
) Decision {
	schemaVersion := payload.SchemaVersion
	if schemaVersion == "" {
		schemaVersion = "1.0"
	}
	return Decision{
		Decision:      decision,
		Route:         decision,
		Score:         score,
		Reason:        reason,
		RequestID:     payload.RequestID,
		SchemaVersion: schemaVersion,
	}
}

func stringFromAny(value any) string {
	if s, ok := value.(string); ok {
		return s
	}
	return ""
}
```

</div>

## Operational Notes

- Recommended FunnelFlux `timeoutMs`: `1000` to `5000` ms for simple functions.
- Maximum FunnelFlux `timeoutMs`: `30000` ms.
- Be aware of external API client timeouts - use some appropriate values.
- For AI-generated functions, require the model to preserve field names exactly
  and keep `body_json.decision` route-compatible.
- HMAC verification examples should be documented separately per platform. These
  templates do not hardcode secrets or assume every destination uses HMAC.
