Handle external callbacks
Handle a provider’s status callback and complete the operation waiting for it.
This example accepts POST /provider/status with an operation ID and status, using a shared bearer token.
Authenticate and publish the event
Section titled “Authenticate and publish the event”public record ProviderStatus(String operationId, String status) {}public record OperationCompleted(String operationId) {}
public class ProviderCallbacks { @HandlePost("/provider/status") WebResponse handle(ProviderStatus body, @HeaderParam("Authorization") String authorization) { String expected = "Bearer " + ApplicationProperties.requireProperty("provider.callback.token"); if (!expected.equals(authorization)) { return WebResponse.builder().status(401).build(); } if ("completed".equals(body.status())) { Fluxzero.publishEvent(new OperationCompleted(body.operationId())); } return WebResponse.builder().status(204).build(); }}data class ProviderStatus(val operationId: String, val status: String)data class OperationCompleted(val operationId: String)
class ProviderCallbacks { @HandlePost("/provider/status") fun handle(body: ProviderStatus, @HeaderParam("Authorization") authorization: String?): WebResponse { val expected = "Bearer " + ApplicationProperties.requireProperty("provider.callback.token") if (expected != authorization) { return WebResponse.builder().status(401).build() } if (body.status == "completed") { Fluxzero.publishEvent(OperationCompleted(body.operationId)) } return WebResponse.builder().status(204).build() }}Configure provider.callback.token through application properties (PROVIDER_CALLBACK_TOKEN in the environment).
Register ProviderCallbacks as a handler. @HeaderParam supplies the header; a missing or incorrect token gets HTTP 401.
Complete and remove the waiting handler
Section titled “Complete and remove the waiting handler”Store OperationStarted before calling the provider, using an operation ID your application already knows.
The stateful handler waits for that ID’s completion:
public record OperationStarted(String operationId) {}public record OperationFinished(String operationId) {}
@Statefulpublic record AwaitingCallback(@EntityId @Association String operationId) { @HandleEvent static AwaitingCallback start(OperationStarted event) { return new AwaitingCallback(event.operationId()); }
@HandleEvent AwaitingCallback complete(OperationCompleted event) { Fluxzero.publishEvent(new OperationFinished(operationId)); return null; }}data class OperationStarted(val operationId: String)data class OperationFinished(val operationId: String)
@Statefuldata class AwaitingCallback(@EntityId @Association val operationId: String) { companion object { @JvmStatic @HandleEvent fun start(event: OperationStarted) = AwaitingCallback(event.operationId) }
@HandleEvent fun complete(event: OperationCompleted): AwaitingCallback? { Fluxzero.publishEvent(OperationFinished(operationId)) return null }}Register AwaitingCallback.class (AwaitingCallback::class.java in Kotlin). @Association finds the waiting instance;
returning null deletes it. Once that deletion is stored, another callback has no waiting instance to complete.
Send the callback twice
Section titled “Send the callback twice”var callback = WebRequest.post("/provider/status") .header("Authorization", "Bearer test-token") .body(new ProviderStatus("op-1", "completed")).build();
TestFixture.create(new ProviderCallbacks(), AwaitingCallback.class) .withProperty("provider.callback.token", "test-token") .givenEvents(new OperationStarted("op-1")) .whenWebRequest(callback) .expectEvents(new OperationFinished("op-1")) .andThen() .whenWebRequest(callback) .expectNoEventsLike(OperationFinished.class);val callback = WebRequest.post("/provider/status") .header("Authorization", "Bearer test-token") .body(ProviderStatus("op-1", "completed")).build()
TestFixture.create(ProviderCallbacks(), AwaitingCallback::class.java) .withProperty("provider.callback.token", "test-token") .givenEvents(OperationStarted("op-1")) .whenWebRequest(callback) .expectEvents(OperationFinished("op-1")) .andThen() .whenWebRequest(callback) .expectNoEventsLike(OperationFinished::class.java)Use HTTPS. For a provider that signs callbacks, follow its signature protocol instead of this bearer-token example. See stateful handlers for persistence and retry behavior, and API integrations for the outgoing call.
© 2026 Fluxzero