Actor app-initiated gRPC event streams (Alpha)

Receive actor callbacks over an app-initiated gRPC stream without exposing a server port

By default, Dapr delivers actor callbacks — method invocations, reminders, timers, and deactivations — by calling inbound HTTP or gRPC endpoints on the application. Each actor-hosting pod must expose a server port that the Dapr sidecar can reach.

Starting with Dapr v1.18, actor hosts can instead open a single bidirectional gRPC stream from the app to the sidecar (SubscribeActorEventsAlpha1) and receive all four callback types over that connection. The app is the gRPC client: it dials daprd, opens the stream, and waits for callbacks. No inbound port is required.

This aligns actor callback delivery with how Dapr handles pub/sub streaming subscriptions (SubscribeTopicEventsAlpha1), configuration watch streams, and scheduler job streams.

The Go SDK provides a high-level helper for this API (client.SubscribeActorEvents). Other Dapr SDKs currently expose SubscribeActorEventsAlpha1 only through their generated gRPC client. See SDK support for details.

Why app-initiated streams?

The app-initiated approach is useful when NetworkPolicies restrict inbound traffic to application pods, when actors run in restricted-networking environments, or when you want a single connection-management surface instead of per-callback HTTP/gRPC routes.

Traditional callbacksApp-initiated stream
Connection directionsidecar → appapp → sidecar
App server port requiredYesNo
NetworkPolicy / firewallMust allow sidecar→app inboundOnly app→sidecar outbound needed
Callback typesSeparate endpoints per typeAll four types on one stream
SDK supportAll SDKsGo SDK helper; other SDKs via generated gRPC client
StabilityStableAlpha (v1.18+)

How it works

The protocol follows a request–response pairing over a bidirectional gRPC stream:

  1. App opens the stream. The app calls SubscribeActorEventsAlpha1 on the Dapr gRPC service and sends an initial registration message (SubscribeActorEventsRequestInitialAlpha1) listing the actor types it hosts, together with optional runtime configuration overrides (idle timeout, drain settings, reentrancy).

  2. Dapr acknowledges registration. daprd responds with a SubscribeActorEventsResponseInitialAlpha1 on the stream. An empty message body signals success; errors surface as a gRPC stream error.

  3. Dapr sends callbacks. Whenever an actor method is invoked, a reminder or timer fires, or an actor is deactivated, daprd sends a SubscribeActorEventsResponseAlpha1 message down the stream. Each message carries a unique correlation id.

  4. App responds. The app processes the callback and sends back a SubscribeActorEventsRequestAlpha1 message containing the matching id. The response type determines the action:

    Callback receivedApp sends back
    invoke_request (method call)invoke_response with response payload
    reminder_requestreminder_response (optionally cancel: true to stop the reminder)
    timer_requesttimer_response (optionally cancel: true to stop the timer)
    deactivate_requestdeactivate_response (ack only, no payload)
  5. Error signaling. If the app cannot handle a callback (for example, the actor method does not exist), it sends a request_failed message with the originating id, a gRPC status code, and an optional message. daprd maps codes.NotFound to a permanent non-retryable failure.

Reconnection and rolling restarts

Dapr supports multiple concurrent streams from the same app process. This enables zero-downtime rolling restarts:

  • When a new pod opens a stream, daprd routes all new callbacks to the newest connection.
  • The older connection continues to receive responses for callbacks it already sent; it drains naturally.
  • Once all in-flight work on an older connection completes, that connection can be closed safely.

Apps should reconnect with exponential back-off if the stream is interrupted.

NetworkPolicy and firewall considerations

Because the app initiates the connection, the traffic direction is:

app pod → daprd sidecar (gRPC port, default 50001)

In environments with restrictive NetworkPolicies, this means you no longer need a rule that allows the sidecar to initiate inbound connections to the app pod. However, you do need egress from the app pod to the sidecar’s gRPC port.

Example NetworkPolicy (Kubernetes)

The following policy restricts ingress to actor-hosting pods and allows egress to the sidecar gRPC port. Adjust the port if you set dapr.io/grpc-port to a non-default value.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: actor-app-initiated-stream
spec:
  podSelector:
    matchLabels:
      app: my-actor-service
  policyTypes:
    - Ingress
    - Egress
  ingress: []  # no inbound rules required for app-initiated streams
  egress:
    - ports:
        - protocol: TCP
          port: 50001   # daprd gRPC port (adjust if changed via annotation)

SDK support

The Go SDK provides a high-level helper, client.SubscribeActorEvents, that manages the stream lifecycle, callback dispatch, and reconnection for you. Other Dapr SDKs currently expose SubscribeActorEventsAlpha1 only through their generated gRPC client; call it directly as shown in the how-to guide.

Last modified July 13, 2026: Fix baseUrl for current (#5242) (d29da20)