Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.useterse.ai/llms.txt

Use this file to discover all available pages before exploring further.

In the Hybrid deployment, Terse Cloud runs the control plane and you run the data plane. Your createJob() registry and every onTrigger closure execute inside a Node process you operate, behind your network, with your environment variables and secrets. If you want to run the control plane in your network as well, see Self-hosting the control plane. For an overview of all three deployment options, see Hosting overview.
Self-hosting the data plane is about where handlers execute, not about running your own event processor. Trigger ingestion, webhook URLs, scheduling, integration auth, signing, retries, and run history all stay on the Terse Cloud control plane.

When the Hybrid deployment is the right choice

Pick this when your workflow needs something only your environment can provide.
ReasonExamples
Private data accessInternal Postgres, Snowflake on a private link, a service mesh, an on-prem warehouse
Secrets in your own envCredentials managed in AWS Secrets Manager, GCP Secret Manager, Vault, or your platform’s vars
VPC-only servicesInternal APIs, microservices, or databases that are not reachable from public infrastructure
Compliance and data residencyCustomer data must stay in a specific region, account, or tenancy
Reusing existing app codeYou already have a Node service with shared libraries, models, or clients you want to import
Custom runtime needsSpecific Node version, native modules, large dependencies, or a long-running process you operate
If none of those apply, the default Managed deployment is simpler. There is nothing to operate, and triggers, scheduling, and runs are fully managed.

Setting it up

1

Attach the project

From the root of your existing repo, run:
terse attach
This authenticates, links the repo to a Terse project, and writes terse.config.json with the self-hosted data plane enabled. See terse attach for the full flow.
2

Point Terse at your server

Set remoteServerUrl to the public URL of your service:
terse.config.json
{
    "projectId": "proj_123",
    "name": "my-app",
    "selfHosted": true,
    "remoteServerUrl": "https://your-app.example.com"
}
3

Set the required environment variables

Your data plane needs two values from the control plane, both available in the dashboard under your project’s settings:
.env
TERSE_API_KEY=terse_...
TERSE_SIGNING_SECRET=whsec_...
VariablePurpose
TERSE_API_KEYAuthenticates outbound calls from your data plane to the control plane: opening a session, executing tools, requesting approvals.
TERSE_SIGNING_SECRETVerifies that incoming trigger requests really came from the control plane. Used as the HMAC key for the x-terse-signature header.
Treat TERSE_SIGNING_SECRET like any other webhook secret. Never log it, never ship it to the client, never commit it. Rotate it from the dashboard if it leaks.
Load both into your process the same way you load any other secret — .env for local development, your platform’s secret store in production.
4

Mount the trigger handler

Expose the SDK’s webhook handler on your HTTP server so the control plane can deliver events to your data plane. Mount it at TERSE_JOB_WEBHOOK_TRIGGER_PATH so URLs match what the platform expects.
src/server.ts
import express from "express"
import { Terse, TERSE_JOB_WEBHOOK_TRIGGER_PATH } from "terse-sdk"
import "./terse.jobs"

const app = express()
app.use(express.json())

const terse = new Terse()

app.post(TERSE_JOB_WEBHOOK_TRIGGER_PATH, async (req, res) => {
    try {
        const result = await terse.handleTrigger(req.body, req.headers)
        res.json(result)
    } catch (err) {
        res.status(401).json({ error: (err as Error).message })
    }
})

app.listen(3000)
The side-effect import of ./terse.jobs (or whichever entry file holds your createJob() calls) is what makes the registry available at request time. handleTrigger reads TERSE_SIGNING_SECRET and TERSE_API_KEY from the process env on every call. See Terse in the SDK reference for the full client surface.
5

Deploy your code, then sync workflows

Ship your data plane the way you normally ship Node code (Render, Fly, ECS, your own Kubernetes, …). Once it’s reachable at remoteServerUrl, run:
terse deploy
The CLI reads remoteServerUrl, registers your workflows on the control plane, and routes every future trigger to your server.
You can keep using terse test, terse history, and terse replay exactly as you would with managed workflows. They run your local code against real sample events and the stored event stream from the control plane.

Identity verification

handleTrigger verifies every incoming request against TERSE_SIGNING_SECRET and rejects anything older than 5 minutes or with a mismatched signature. Failing requests never reach your handler.
If you parse the request body before calling handleTrigger (for example with express.json()), the SDK re-serializes it with JSON.stringify to recompute the signature. Don’t mutate req.body between parsing and the call, or verification will fail.

Who runs what

Your data planeTerse Cloud control plane
The Node process that loads src/terse.jobs.tsTrigger ingestion from integrations (Attio, Slack, …)
Every createJob() registrationWebhook URLs, signing, and delivery
Every onTrigger handler closureCron and interval scheduling
Every agent.tools.* call your handler makesIntegration auth and tool definitions
Internal clients, DB drivers, or SDKs you importRun history, action traces, and approvals
The HTTP endpoint that receives signed payloadsThe dashboard
When a trigger fires, the control plane signs the event and POSTs it to your data plane. Your server verifies the signature with handleTrigger, looks up the registered workflow by name, and runs your onTrigger closure in your process. Tool calls, agent runs, and approvals flow back to the control plane so Activity, Notifications, and history all keep working.

What stays the same

Even though execution moves into your infrastructure, the only difference is the box your onTrigger runs inside. createJob(), agent.tools.*, runs, approvals, and the Activity tab all keep working as they do for managed workflows.

Where to go next

Hosting overview

The control plane / data plane split and the three deployment options.

Self-host the control plane

Run the orchestrator in your network with npx create-terse.

`terse attach`

CLI reference for linking an existing repo to a self-hosted data plane.

`Terse` client

SDK reference for handleTrigger and signed payload verification.