GC
9 min read·Updated 2026-04-13

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:

Authenticating a request
bash
# 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.

POST /api/hooks/wake
bash
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"
  }'
Response (200 OK)
json
{
  "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.

POST /api/hooks/agent (async)
bash
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"
  }'
Response (202 Accepted)
json
{
  "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 within timeoutSeconds.
  • 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 responseJsonSchema is the empty object {}, is invalid as a Draft 2020-12 schema, or was sent without syncResponse: 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:

  1. 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 without syncResponse: true is rejected — a schema only makes sense on a synchronous call.
  2. 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.
  3. 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.
POST /api/hooks/agent (synchronous schema)
bash
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:

Response (200 OK)
json
{
  "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:

  1. No schema. responseValidationModelTier is ignored — it is only meaningful alongside a schema. The turn runs on model, or the normal default if model is omitted.
  2. Schema, no responseValidationModelTier. The turn runs on model (or default). Repair calls also run on model; if that is unset they fall back to balanced.
  3. Schema + responseValidationModelTier. responseValidationModelTier takes over both the main schema-constrained turn and the repair calls. model is 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:

Error response body
json
{
  "status": "error",
  "errorMessage": "Human-readable explanation"
}

What to do next