Multi-namespace Orchestration
Run multiple workflows for the same entity across namespaces.
Use multi-namespace orchestration when one entity needs multiple independent evaluations. Common examples include object-level review in one namespace and account-level actions in another.
When to use this pattern
Section titled “When to use this pattern”- separate policy ownership by workflow
- different action semantics by namespace
- independent iteration and rollout per workflow
Core model
Section titled “Core model”- one real-world entity can appear in multiple namespaces
- each namespace submission returns its own
requestId - webhook processing is independent per namespace
- your system can merge outcomes with deterministic precedence rules
Recommended conventions
Section titled “Recommended conventions”- keep IDs stable for each namespace identity (
message_123,user_456, etc.) - persist
(namespace, id, requestId)for every submission - make handlers idempotent on webhook delivery retries
- decide conflict precedence up front
Ingest pattern
Section titled “Ingest pattern”import Safetykit from "safetykit";
const client = new Safetykit({ apiKey: process.env.SAFETYKIT_API_KEY });
const messageResponse = await client.data.ingest("messages", { data: { id: "message_12345", user_id: "user_67890", text: "sample content", sent_at: "2026-02-27T12:00:00.000Z", },});
const userResponse = await client.data.ingest("users", { data: { id: "user_67890", latest_message_id: "message_12345", },});
console.log(messageResponse.requestId, userResponse.requestId);from safetykit import Safetykit
client = Safetykit(api_key="sk_your_api_key")
message_response = client.data.ingest("messages", { "data": { "id": "message_12345", "user_id": "user_67890", "text": "sample content", "sent_at": "2026-02-27T12:00:00.000Z" }})
user_response = client.data.ingest("users", { "data": { "id": "user_67890", "latest_message_id": "message_12345" }})
print(message_response.request_id, user_response.request_id)import java.net.URIimport java.net.http.HttpClientimport java.net.http.HttpRequestimport java.net.http.HttpResponse
val client = HttpClient.newHttpClient()val apiKey = System.getenv("SAFETYKIT_API_KEY")
fun ingest(namespace: String, body: String): HttpResponse<String> { val request = HttpRequest.newBuilder() .uri(URI.create("https://api.safetykit.com/v1/data/$namespace")) .header("Authorization", "Bearer $apiKey") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build()
return client.send(request, HttpResponse.BodyHandlers.ofString())}
val messageResponse = ingest("messages", """ { "data": { "id": "message_12345", "user_id": "user_67890", "text": "sample content", "sent_at": "2026-02-27T12:00:00.000Z" } }""".trimIndent())
val userResponse = ingest("users", """ { "data": { "id": "user_67890", "latest_message_id": "message_12345" } }""".trimIndent())
println("${messageResponse.body()} ${userResponse.body()}")import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();String apiKey = System.getenv("SAFETYKIT_API_KEY");
HttpResponse<String> ingest(String namespace, String body) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.safetykit.com/v1/data/" + namespace)) .header("Authorization", "Bearer " + apiKey) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build();
return client.send(request, HttpResponse.BodyHandlers.ofString());}
HttpResponse<String> messageResponse = ingest("messages", """ { "data": { "id": "message_12345", "user_id": "user_67890", "text": "sample content", "sent_at": "2026-02-27T12:00:00.000Z" } } """);
HttpResponse<String> userResponse = ingest("users", """ { "data": { "id": "user_67890", "latest_message_id": "message_12345" } } """);
System.out.println(messageResponse.body() + " " + userResponse.body());Webhook handling pattern
Section titled “Webhook handling pattern”if (event.type !== "workflow.succeeded") return;
if (event.namespace === "messages") { await storeMessageDecision(event.id, event.output);}
if (event.namespace === "users") { await applyUserAction(event.id, event.output);}if event["type"] != "workflow.succeeded": return
if event["namespace"] == "messages": await store_message_decision(event["id"], event["output"])
if event["namespace"] == "users": await apply_user_action(event["id"], event["output"])if (event["type"] != "workflow.succeeded") return
if (event["namespace"] == "messages") { storeMessageDecision(event["id"], event["output"])}
if (event["namespace"] == "users") { applyUserAction(event["id"], event["output"])}if (!event.get("type").equals("workflow.succeeded")) { return;}
if (event.get("namespace").equals("messages")) { storeMessageDecision(event.get("id"), event.get("output"));}
if (event.get("namespace").equals("users")) { applyUserAction(event.get("id"), event.get("output"));}Decision reconciliation
Section titled “Decision reconciliation”A practical merge model is:
- Persist each namespace decision independently.
- Resolve final platform action with deterministic precedence.
- Record which namespaces contributed to the final decision for auditability.
For the canonical webhook payload contract, see workflow.succeeded.