Skip to Content
How-To GuidesGoUsing Webhooks in a Go Golem Agent

Using Webhooks in a Go Golem Agent

Overview

A Golem webhook lets an agent mint a temporary public URL that an external system POSTs to; the POST body is delivered back to the agent. Under the hood a webhook is a Golem promise: the agent durably suspends while awaiting the callback, consuming no resources, and survives restarts (exactly-once via oplog replay).

A webhook is a promise completed by an HTTP POST:

  • golem.NewWebhook[T]() creates the promise and the URL that completes it; w.URL() is the URL to hand out.
  • w.ID() returns the golem.PromiseID to persist in agent state.
  • golem.PromiseByID[T](id).MustAwait() (or w.MustAwait()) suspends until the POST arrives and returns the body decoded as T; Await() returns (T, error) instead.

This is useful for payment gateways, CI/CD, GitHub/Stripe callbacks, and any event-driven workflow where an external system notifies the agent.

Prerequisites

The agent type must have an HTTP mount (Spec.HTTP = &golem.Mount{Path: "/..."}) and be deployed via an httpApi deployment in golem.yaml. Without that, NewWebhook traps (it is fail-loud). The mount does not need any HTTP endpoints — it only makes the agent HTTP-API-deployable.

Steps

  1. Add an HTTP mount to the agent’s Spec (see golem-add-http-endpoint-go).
  2. Create a webhook; persist its PromiseID in state and return/share the URL.
  3. Await the callback in a later invocation via the stored id.
  4. Deploy behind an httpApi so the URL is reachable.

API

Function / methodDescription
golem.NewWebhook[T]()Create a promise and the public URL that a POST completes; returns *golem.Webhook[T] (a *golem.Promise[T] plus URL()); requires the agent be HTTP-API-deployed
(*Promise[T]).WebhookURL() stringMint the URL for a promise created with golem.NewPromise[T]()
(*Promise[T]).ID() golem.PromiseIDThe durable id — persist it to await/complete in a later invocation
(*Promise[T]).Await() (T, error)Suspend until completed; returns the POST body decoded as T (JSON, or raw bytes when T is []byte), or an error if it does not decode
(*Promise[T]).MustAwait() TAwait that panics if the body does not decode
golem.PromiseByID[T](id)Rebuild a handle from a stored PromiseID
golem.CompletePromise[T](id, value)Complete a promise from another agent (external completers use the URL instead)

Complete example

Definition (agents/webhookdemo/webhookdemo.go)

// Package webhookdemo is the DEFINITION of a webhook agent. It needs the HTTP // mount below (plus an httpApi deployment) or NewWebhook traps. package webhookdemo import "github.com/golemcloud/golem/sdks/go/golem" type ID struct{ Queue string } // bound by {queue} in the mount path // Event is the webhook payload — JSON tags are the contract for the caller's POST. type Event struct { OrderID string `json:"orderId"` Amount int64 `json:"amount"` } var Agent = golem.DefineAgent[ID](golem.Spec{ Name: "WebhookDemoAgent", Description: "Completes a promise from an external webhook POST", HTTP: &golem.Mount{Path: "/hooks/{queue}"}, // makes the agent HTTP-API-deployable }) var ( Open = Agent.Method[golem.Unit, string]("open", golem.Description("Create a promise and return its webhook URL")) Result = Agent.Method[golem.Unit, Event]("result", golem.Description("Await the payload POSTed to the webhook URL")) )

Implementation (agents/webhookdemo/impl/impl.go)

package impl import ( "myapp/agents/webhookdemo" "github.com/golemcloud/golem/sdks/go/golem" ) type state struct { pending golem.PromiseID open bool } var agent = webhookdemo.Agent.Implement(func(webhookdemo.ID) *state { return &state{} }) func init() { // Mint a URL; persist the PromiseID so a later invocation can await it. agent.Handle(webhookdemo.Open, func(ctx *golem.Context[state], _ golem.Unit) string { w := golem.NewWebhook[webhookdemo.Event]() ctx.State.pending = w.ID() ctx.State.open = true return w.URL() // hand this URL to the external system }) // Suspend until the external POST arrives; returns the decoded body. agent.Handle(webhookdemo.Result, func(ctx *golem.Context[state], _ golem.Unit) webhookdemo.Event { if !ctx.State.open { panic("no open webhook — call open first") } return golem.PromiseByID[webhookdemo.Event](ctx.State.pending).MustAwait() }) }

Payload decoding

Await() decodes the POST body as its type parameter T via JSON, and returns an error when the body does not decode. Use a struct with json tags (as Event above) for structured payloads, or golem.PromiseByID[[]byte](id).Await() to receive the raw request bytes unchanged.

Webhook URL structure and suffix

The minted URL has the shape https://<domain>/<prefix>/<suffix>/<id>:

  • <domain> — where the HTTP API is deployed.
  • <prefix> — defaults to /webhooks, customizable via webhookUrl in the httpApi deployment section of golem.yaml.
  • <suffix> — defaults to the agent type name in kebab-case; override it with Mount.WebhookSuffix.
  • <id> — a unique per-webhook identifier.
HTTP: &golem.Mount{Path: "/api/orders/{id}", WebhookSuffix: "/workflow-hooks"},

Key Constraints

  • The agent must set Spec.HTTP and be deployed via httpApi, or WebhookURL() panics, which traps the component, as in Rust.
  • Only the agent that created the promise may Await it; awaiting from another agent traps in the host.
  • Only a POST to the URL completes the promise; the URL is one-shot — once completed it is invalid, and a later Await returns the recorded body from the oplog.
  • The agent is durably suspended while awaiting — it survives failures, restarts, and updates.
  • Go difference from Rust: Mount.WebhookSuffix must be literal segments only — path variables like {agent-type} or {name} in the suffix are rejected in the Go SDK (the Rust webhook_suffix allows them).
GuideDescription
golem-add-http-endpoint-goSet up the HTTP mount + httpApi deployment webhooks require
golem-add-agent-goDefine the agent and its payload types
golem-add-http-auth-goRequire authentication on the agent’s other endpoints