Webhooks
Webhooks are an HTTP channel for other systems to talk to the bot. Wake for fire-and-forget events, agent for full runs, and synchronous JSON schemas when the caller needs a typed response back.
What a webhook is
Requires
Webhooks must be turned on in Settings → Webhooks with a token. Until then every webhook endpoint returns 404 Not Found. Wrong or missing tokens return 401 Unauthorized.
A webhook is just an HTTP request another system sends the bot to make something happen. CI finished a build → ping the bot. An alert fired → ping the bot. GitHub got a push → ping the bot. The bot ships two built-in endpoints and lets you define your own with a small mapping.
Webhooks are a channel of their own — they go through the same agent loop as Telegram or the web chat, but they have a few channel-specific rules that matter:
- Memory is off by default for webhook turns. See Memory in webhooks below.
- Session tier stickiness (
/tier) is excluded for the webhook channel — each run resolves its tier from the request, not from a previous session.
Turning webhooks on
Go to Settings → Webhooks and flip the toggle on. Set a token — this becomes the bearer token that every built-in endpoint checks. You can send it two ways:
# Standard
curl -H "Authorization: Bearer your-webhook-token" ...
# Alternative for services that can't set Authorization
curl -H "X-Golemcore-Token: your-webhook-token" ...Custom hooks can use bearer too, or switch to HMAC-SHA256 with a per-hook secret — see Custom hooks.
Wake vs agent
Pick the endpoint by asking what the caller needs back:
Wake
Fire-and-forget. The bot receives a short event message, starts processing it in the background, and the caller gets 200 OK immediately. Use this when the external system just needs to notify the bot that something happened and does not care about the reply.
POST /api/hooks/wake
Agent
Full agent turn. The bot runs the whole pipeline — tools, memory (if enabled), the works. By default the caller gets 202 Accepted plus a run ID and the reply is delivered asynchronously. With syncResponse: true the caller waits and receives the reply in the same HTTP response.
POST /api/hooks/agent
Both endpoints actually run through the agent loop — the difference is not “does work happen” but what the caller gets back and when. Wake is the right choice when you have nothing to do with the answer. Agent is the right choice when the caller needs to see the answer, either through a callback URL or by waiting.
The wake endpoint
The wake endpoint takes a short text event and acknowledges it immediately. The agent loop runs in the background; the HTTP caller never sees the reply.
curl -X POST http://localhost:8080/api/hooks/wake \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-webhook-token" \
-d '{
"text": "Build completed successfully",
"chatId": "webhook:ci"
}'{
"status": "accepted",
"chatId": "webhook:ci"
}text is the only required field. chatId is optional and defaults to webhook:default. An optional metadata object is passed through to the message. The request body is sanitized and wrapped with [EXTERNAL WEBHOOK DATA] safety markers before it reaches the model.
The agent endpoint
The agent endpoint runs a full turn. The field is message, not text. By default it returns 202 Accepted with a run ID — the real reply is delivered later via callback URL, cross-channel delivery, or can be read from the dashboard.
curl -X POST http://localhost:8080/api/hooks/agent \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-webhook-token" \
-d '{
"message": "Review deployment logs for errors",
"model": "smart",
"callbackUrl": "https://my-server.com/webhook-results"
}'{
"status": "accepted",
"runId": "550e8400-e29b-41d4-a716-446655440000",
"chatId": "hook:550e8400-e29b-41d4-a716-446655440001"
}All request fields, in one place:
message
The prompt for the agent. Required.
string
chatId
Session identifier. Defaults to a fresh hook:<uuid>.
string
name
Human-readable label for logging. Optional.
string
model
Model tier override. Accepts the four named tiers (balanced, smart, coding, deep) and the five custom slots (special1–special5). See the tier resolution section for priority rules when a schema is present.
string
memoryPreset
Memory preset for this one run. Overrides the webhook-level default (which itself defaults to disabled). Must be a known preset id — see Memory in webhooks below.
string
syncResponse
Wait for the agent to finish and return the result in this same HTTP response. Required whenever responseJsonSchema is set.
boolean, default false
responseJsonSchema
Draft 2020-12 JSON Schema the agent output must match. Forces synchronous mode. Empty objects are rejected. See Synchronous responses below.
object
responseValidationModelTier
Model tier used for the schema-constrained agent run and the repair calls. Only honored when responseJsonSchema is set.
string
callbackUrl
URL to POST the final result to when the run finishes. Independent from syncResponse — both can be set at once.
string
deliver / channel / to
Route the reply to a messaging channel. Set deliver, channel (e.g. telegram), and to (chat ID on that channel).
object
timeoutSeconds
Max execution time. Defaults to webhooks.defaultTimeoutSeconds (300).
int
metadata
Arbitrary pass-through object attached to the message metadata.
object
Synchronous responses
Set syncResponse: true on an agent request to make the HTTP caller wait for the completed result instead of receiving a run ID. The endpoint holds the request open for up to timeoutSeconds (default 300) and returns the final answer in the same response body.
There are two sub-modes, and they behave differently:
Without a schema
No responseJsonSchema set. The agent runs a normal turn and its final answer is returned as text/plain. The response includes X-Golemcore-Run-Id and X-Golemcore-Chat-Id headers so the caller can still correlate it later.
Plain sync
With a schema
responseJsonSchema is set. The bot adds schema instructions to the system prompt, runs the turn, validates the result against the schema, and — if validation fails — makes up to 3 repair calls. The successful HTTP body is the validated JSON payload itself (not wrapped, not escaped). An X-Golemcore-Schema-Repair-Attempts header reports how many repairs it took.
Schema sync
A callbackUrl can be combined with either sub-mode. The callback payload is the raw agent text and is delivered independently — the schema contract only applies to the synchronous HTTP body.
Error codes specific to sync mode
504 Gateway Timeout— the agent did not finish withintimeoutSeconds.422 Unprocessable Entity— schema processing failed (for example, the agent output could not be made to match the schema after 3 repair attempts).400 Bad Request—responseJsonSchemais the empty object{}, is invalid as a Draft 2020-12 schema, or was sent withoutsyncResponse: true.
Response JSON schemas
A response schema turns a webhook into a contract: the caller declares the shape it wants back, and the bot is required to produce exactly that. This is how you make a webhook behave like a typed RPC.
Three things happen when you pass a responseJsonSchema:
- Pre-flight check. The bot validates the schema itself as a Draft 2020-12 JSON Schema before it accepts the run. An empty
{}is rejected, and a schema withoutsyncResponse: trueis rejected — a schema only makes sense on a synchronous call. - Schema-aware turn.The schema is rendered into the agent's system prompt so the model knows the exact output shape it must produce. The model tier for this turn follows the tier resolution rules below.
- Validate and repair. The final agent output is parsed and validated against the schema. If it does not match, the bot makes up to 3 repair calls, feeding the validation errors back to the model each time. If all 3 repair calls still fail, the response returns
422.
curl -X POST http://localhost:8080/api/hooks/agent \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-webhook-token" \
-d '{
"message": "Classify the customer report and write a one-line summary.",
"syncResponse": true,
"model": "smart",
"responseValidationModelTier": "balanced",
"responseJsonSchema": {
"type": "object",
"required": ["version", "result"],
"additionalProperties": false,
"properties": {
"version": { "const": "1.0" },
"result": {
"type": "object",
"required": ["category", "severity", "summary"],
"additionalProperties": false,
"properties": {
"category": { "type": "string", "enum": ["bug", "question", "feature"] },
"severity": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
}
}
}
}
}'All of model, responseValidationModelTier, syncResponse, and responseJsonSchemaare first-class fields on the agent request body — each call chooses its own tier, sync mode, and schema independently of the webhook's defaults. A successful response for the request above is the validated JSON payload itself, not wrapped and not stringified:
{
"version": "1.0",
"result": {
"category": "bug",
"severity": "medium",
"summary": "Upload fails with 500 when the file is larger than 10 MB."
}
}Headers on a schema-sync success: X-Golemcore-Run-Id, X-Golemcore-Chat-Id, and X-Golemcore-Schema-Repair-Attempts (number of repair calls it took, 0 if the first attempt matched).
How tier settings are resolved
Two fields can influence the model tier: model (normal request-level override) and responseValidationModelTier (a schema-specific override). The rule is:
- No schema.
responseValidationModelTieris ignored — it is only meaningful alongside a schema. The turn runs onmodel, or the normal default ifmodelis omitted. - Schema, no responseValidationModelTier. The turn runs on
model(or default). Repair calls also run onmodel; if that is unset they fall back tobalanced. - Schema + responseValidationModelTier.
responseValidationModelTiertakes over both the main schema-constrained turn and the repair calls.modelis superseded for this run.
Separately, the /tiercommand's per-session stickiness does not apply to the webhook channel at all. Webhook runs always start from scratch tier-wise and use only the rules above.
Custom hooks
A custom hook is a mapping you define so an external system can send its own JSON shape — GitHub push events, Stripe charges, alert payloads — and the bot turns it into a message. Each custom hook lives at POST /api/hooks/<name>.
You create custom hooks in Settings → Webhooks. Every field on the async /agent endpoint is available on a mapping too, plus these:
name
URL slug — the hook is reachable at /api/hooks/<name>. Must be unique across hooks.
Required
action
What to do with the request: wake (fire-and-forget) or agent (full turn). Defaults to wake.
Default: wake
authMode
How to authenticate: bearer (shared webhook token) or hmac (per-hook signature on a header). Defaults to bearer.
Default: bearer
hmacHeader / hmacSecret / hmacPrefix
HMAC only. The header that carries the signature (e.g. x-hub-signature-256), the shared secret, and an optional prefix to strip (e.g. sha256=). HMAC-SHA256 with constant-time comparison against the raw body.
HMAC fields
messageTemplate
The message handed to the bot. Use {json.path} placeholders to pull values from the incoming JSON body. When blank, the raw JSON body is used as the message.
Template
agent fields
Same sync-response, responseJsonSchema, responseValidationModelTier, model, deliver/channel/to fields as the /agent endpoint. They apply when action is agent.
Mirror of /agent
Placeholders use {json.path}, not Mustache
Placeholders are {json.path} with a single set of braces. Bare dotted paths are normalized to JSONPath automatically, so {repository.full_name}, {tags[0]}, and full expressions like {$.payload.meta.client_id} are all supported. Missing keys render as <missing>. Mustache-style double braces are not supported.
Memory in webhooks
Webhook traffic does not read from or write to global memory by default. The webhook configuration has a memoryPreset field that defaults to disabled, which means:
- The agent loop does not recall notes at the start of a webhook turn.
- The memory tool does not persist anything the bot tries to save during a webhook turn.
To opt in, set webhooks.memoryPreset in Settings → Webhooks to any of the known memory preset ids (coding_balanced, coding_fast, coding_deep, general_chat, research_analyst, ops_support). An individual /agent request can also pass memoryPreset in the body to override the webhook default for one call — unknown preset ids return 400.
This is on purpose: webhooks are often triggered by external, untrusted systems, and mixing their payloads into long-term memory can quickly pollute the notebook. Start with memory off; turn it on only for hooks you trust. See Memory for what the presets do.
Error responses
Wake accepted
Wake accepted. Fire-and-forget path.
200 OK
Agent accepted
Agent run accepted (async). The body contains runId and chatId.
202 Accepted
Sync success
Schema-sync success. Body is plain text or the validated JSON payload depending on whether responseJsonSchema was set.
200 OK (sync)
Bad Request
Missing required field (text for /wake, message for /agent), malformed JSON body, invalid callback URL, empty {} schema, or schema without syncResponse: true.
400
Unauthorized
Missing, wrong, or unverifiable bearer token (or failing HMAC signature on a custom hook).
401
Not Found
Webhooks are disabled in Settings, or the custom hook name does not match any mapping.
404
Payload Too Large
Payload exceeds webhooks.maxPayloadSize (default 64KB). Applies to custom hooks.
413
Schema processing failed
Schema-sync only. The agent output could not be made to match the schema within 3 repair attempts.
422
Gateway Timeout
Sync mode only. The agent did not finish within timeoutSeconds (default 300).
504
Every error response carries the same small JSON body:
{
"status": "error",
"errorMessage": "Human-readable explanation"
}What to do next
Related pages
User Guide
Dashboard
A tour of the dashboard — what each page is for, where to find settings, and how to read what the bot is doing.
User Guide
Memory
How GolemCore remembers facts across conversations, what the seven memory presets do, and how to pick one in Settings.
User Guide
Model Routing
How GolemCore Bot picks a model for each turn, the four named tiers plus five custom slots, and how to set them up in Settings.
Cookbook
Monitor CI with Webhooks
Expose a custom webhook endpoint that turns CI failure payloads into agent prompts. Wired in Settings → Webhooks, verified with curl, then hooked into GitHub Actions.