External Events
External Events allow your workflows to react to input from the outside world. You can think of an ExternalEvent
as a callback or webhook which signals something to your WfRun
.
In order to use External Events in your workflows, you need to:
- Create an
ExternalEventDef
, which tells the LittleHorse Server about the type of callback. - Create a
WfSpec
that uses thatExternalEventDef
. - Run the
WfSpec
to create aWfRun
. - Post an
ExternalEvent
to theWfRun
to complete it.
For the sake of simplicity, every code sample in this document can be compiled and run on its own as a single file. You can even mix-and-match between different languages—try doing one step in Java and another in Python!
If you do want to follow along with the code here, we recommend checking out our installation docs to:
- Install
lhctl
. - Set up a LittleHorse Server for local development.
- Add the dependency for your SDK of choice.
Setting up Metadata
In this section, we will create a TaskDef
, ExternalEventDef
, and WfSpec
for our example. The example we will build is a workflow in which:
- We wait for an
ExternalEvent
of typename-posted
. - We pass the payload from that
ExternalEvent
into agreet
TaskRun
.
Background: The TaskDef
Let's use a TaskDef
called greet
, and deploy a task worker for it all at the same time. This example focuses on User Tasks, so we won't go into detail about how the Task Worker works.
Run the following code in a terminal to register the TaskDef
and run your Task Worker. Keep it running for the duration of this exercise so that the worker can execute TaskRun
s once you run your WfRun
.
- Java
- Go
- Python
- DotNet
package io.littlehorse.quickstart;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.worker.LHTaskMethod;
import io.littlehorse.sdk.worker.LHTaskWorker;
class Greeter {
@LHTaskMethod("greet")
public String greet(String name) {
String result = "Hello, " + name + "!";
System.out.println(result);
return result;
}
}
public class Main {
public static void main(String[] args) {
LHConfig config = new LHConfig();
// Create a Task Worker
LHTaskWorker worker = new LHTaskWorker(new Greeter(), "greet", config);
Runtime.getRuntime().addShutdownHook(new Thread(worker::close));
// Register the TaskDef
worker.registerTaskDef();
// Start the Worker
worker.start();
}
}
TODO
TODO
TODO
Create the ExternalEventDef
An ExternalEventDef
registers a type of callback with the LittleHorse Server. Before you can use an ExternalEvent
in your workflows, you need to create the ExternalEventDef
. You can do it as follows.
- Java
- Go
- Python
- DotNet
package io.littlehorse.quickstart;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.ExternalEventDef;
import io.littlehorse.sdk.common.proto.PutExternalEventDefRequest;
import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub;
public class Main {
public static final String EXTERNAL_EVENTDEF_NAME = "name-posted";
public static void main(String[] args) {
LHConfig config = new LHConfig();
LittleHorseBlockingStub client = config.getBlockingStub();
PutExternalEventDefRequest request = PutExternalEventDefRequest.newBuilder()
.setName(EXTERNAL_EVENTDEF_NAME)
.build();
ExternalEventDef result = client.putExternalEventDef(request);
System.out.println(LHLibUtil.protoToJson(result));
}
}
For more information about registering an ExternalEventDef
, check out the Managing Metadata docs.
Once you run the code, you should have some output like the following:
{
"id": {
"name": "name-posted"
},
"createdAt": "2025-01-29T07:29:54.223Z",
"retentionPolicy": {
}
}
Registering the WfSpec
Let's register our WfSpec
! The key method here is waitForEvent
, which does two things:
- Block the
WfRun
until anExternalEvent
of thename-posted
type is sent to thisWfRun
. - Give us a handle that allows us to access the payload of the
ExternalEvent
(in this case, we assign thename
variable to the payload).
- Java
- Go
- Python
- DotNet
package io.littlehorse.quickstart;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.Workflow;
import io.littlehorse.sdk.wfsdk.WorkflowThread;
public class Main {
public static final String EXTERNAL_EVENTDEF_NAME = "name-posted";
public static void wfLogic(WorkflowThread wf) {
WfRunVariable name = wf.declareStr("name");
// Wait for the ExternalEvent
NodeOutput eventPayload = wf.waitForEvent(EXTERNAL_EVENTDEF_NAME);
// Save the payload into the `name` variable
name.assign(eventPayload);
// Pass the `name` variable (which has our event's payload) into the
// greet task
wf.execute("greet", name);
}
public static void main(String[] args) {
LHConfig config = new LHConfig();
Workflow wfGenerator = Workflow.newWorkflow("greet-event", Main::wfLogic);
wfGenerator.registerWfSpec(config.getBlockingStub());
}
}
After registering your WfSpec
with the above code, it should look like the following in the LittleHorse Dashboard:

Run the Workflow
Now that we've registered our TaskDef
, ExternalEventDef
, and WfSpec
, all that remains for us is to run the WfSpec
(and start a WfRun
) and then complete the WfRun
by posting an ExternalEvent
to it.
Start a WfRun
You can start the WfRun
using lhctl
as follows:
lhctl run greet-event
Make note of the WfRunId
(this comes from theid.id
field) as we'll need it in the next step!
If we look at our WfRun
in the dashboard, we'll see that it's waiting on the ExternalEventNode
.

Posting an Event
In LittleHorse, all ExternalEvent
s are associated with a WfRun
. Therefore, to post an ExternalEvent
, you need:
- The correlated
WfRunId
. - The
ExternalEventDefId
that tells LittleHorse what type of event we are posting. - An optional payload that we want to send to the
WfRun
.
You can post an ExternalEvent
using our SDK's or by using lhctl
. With lhctl
, it's simple:
lhctl postEvent <wfRunId> name-posted STR "Obi-Wan Kenobi"
We first provide the WfRun Id, then the name of the ExternalEventDef
, then the type of the payload, and then finally the actual payload.
Using the SDK's, we can do the above in code as follows:
- Java
- Go
- Python
- DotNet
package io.littlehorse.quickstart;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.ExternalEvent;
import io.littlehorse.sdk.common.proto.ExternalEventDefId;
import io.littlehorse.sdk.common.proto.PutExternalEventRequest;
import io.littlehorse.sdk.common.proto.WfRunId;
public class Main {
public static void main(String[] args) {
LHConfig config = new LHConfig();
WfRunId wfRunId = WfRunId.newBuilder()
.setId("e27af9ea516749fda454202101c734f8")
.build();
ExternalEventDefId eventDefId = ExternalEventDefId.newBuilder()
.setName("name-posted")
.build();
PutExternalEventRequest request = PutExternalEventRequest.newBuilder()
.setWfRunId(wfRunId)
.setExternalEventDefId(eventDefId)
.setContent(LHLibUtil.objToVarVal("Obi-Wan Kenobi"))
.build();
ExternalEvent result = config.getBlockingStub().putExternalEvent(request);
System.out.println(LHLibUtil.protoToJson(result));
}
}
The output of either the lhctl
command or the code will look something like:
{
"id": {
"wfRunId": {
"id": "e27af9ea516749fda454202101c734f8"
},
"externalEventDefId": {
"name": "name-posted"
},
"guid": "9062b812ecb54328812ec32273791ecd"
},
"createdAt": "2025-01-29T08:01:57.348Z",
"content": {
"str": "Obi-Wan Kenobi"
},
"threadRunNumber": 0,
"nodeRunPosition": 1,
"claimed": true
}
That output is the actual ExternalEvent
object in the LittleHorse API. Cool!
You'll also notice that the WfRun
has completed, and the Task Worker said hello to Obi-Wan Kenobi
:
Hello, Obi-Wan Kenobi!
Further Resources
Congrats on learning how to use ExternalEvent
s in your LittleHorse workflows! There's a lot more you can do with ExternalEvent
s, though, so keep on reading:
- Timeouts on your
ExternalEventNode
s with the.timeoutSeconds()
method. - Idempotency when posting
ExternalEvent
s. - Viewing the
ExternalEvent
object vialhctl get externalEvent <wfRunId> <externalEventDefName> <guid>
- Play around with
lhctl get externalEvent <wfRunId> <externalEventDefName> <guid>
andlhctl list nodeRun <wfRunId>
and see if you can find out the relationship between them!