diff --git a/ZB.MOM.WW.Health/src/ZB.MOM.WW.Health/GrpcDependencyHealthCheck.cs b/ZB.MOM.WW.Health/src/ZB.MOM.WW.Health/GrpcDependencyHealthCheck.cs
new file mode 100644
index 0000000..0c577d8
--- /dev/null
+++ b/ZB.MOM.WW.Health/src/ZB.MOM.WW.Health/GrpcDependencyHealthCheck.cs
@@ -0,0 +1,80 @@
+using Grpc.Core;
+using Grpc.Net.Client;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace ZB.MOM.WW.Health;
+
+///
+/// Health check that verifies a downstream gRPC dependency is reachable over its
+/// .
+///
+///
+///
+/// The probe is injectable via ; the default drives the
+/// channel to a connected state with . The result is
+/// when the probe returns true, and
+/// when it returns false, throws an
+/// , or times out / is cancelled within
+/// .
+///
+///
+/// Recommended registration tags: and
+/// — a missing downstream gRPC dependency makes the node both
+/// not-ready and not-able-to-act. The registrant applies the tags.
+///
+///
+public sealed class GrpcDependencyHealthCheck : IHealthCheck
+{
+ private readonly GrpcChannel _channel;
+ private readonly GrpcDependencyOptions _options;
+
+ /// Initializes a new .
+ /// The gRPC channel to the downstream dependency.
+ ///
+ /// Probe, dependency name, and timeout. When null, defaults are used (the default probe is
+ /// with a 5 s timeout).
+ ///
+ public GrpcDependencyHealthCheck(GrpcChannel channel, GrpcDependencyOptions? options = null)
+ {
+ _channel = channel ?? throw new ArgumentNullException(nameof(channel));
+ _options = options ?? new GrpcDependencyOptions();
+ }
+
+ ///
+ public async Task CheckHealthAsync(
+ HealthCheckContext context,
+ CancellationToken cancellationToken = default)
+ {
+ var name = _options.DependencyName ?? "gRPC dependency";
+ var probe = _options.Probe ?? DefaultProbeAsync;
+
+ using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ timeoutCts.CancelAfter(_options.Timeout);
+
+ try
+ {
+ var reachable = await probe(_channel, timeoutCts.Token).ConfigureAwait(false);
+ return reachable
+ ? HealthCheckResult.Healthy($"{name} is reachable.")
+ : HealthCheckResult.Unhealthy($"{name} is unreachable.");
+ }
+ catch (RpcException ex)
+ {
+ return HealthCheckResult.Unhealthy($"{name} probe failed: {ex.Status.StatusCode}.", ex);
+ }
+ catch (OperationCanceledException ex) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
+ {
+ return HealthCheckResult.Unhealthy($"{name} probe timed out after {_options.Timeout}.", ex);
+ }
+ }
+
+ ///
+ /// Default probe: connects the channel and reports reachability. Returns true once the
+ /// channel reaches a connected state; surfaces failures as a thrown exception (handled by the caller).
+ ///
+ private static async Task DefaultProbeAsync(GrpcChannel channel, CancellationToken cancellationToken)
+ {
+ await channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
+ return true;
+ }
+}
diff --git a/ZB.MOM.WW.Health/src/ZB.MOM.WW.Health/GrpcDependencyOptions.cs b/ZB.MOM.WW.Health/src/ZB.MOM.WW.Health/GrpcDependencyOptions.cs
new file mode 100644
index 0000000..bd42812
--- /dev/null
+++ b/ZB.MOM.WW.Health/src/ZB.MOM.WW.Health/GrpcDependencyOptions.cs
@@ -0,0 +1,26 @@
+using Grpc.Net.Client;
+
+namespace ZB.MOM.WW.Health;
+
+///
+/// Options for .
+///
+public sealed class GrpcDependencyOptions
+{
+ ///
+ /// The reachability probe. Returns true when the dependency is reachable, false
+ /// otherwise. When null the default probe is used: ,
+ /// which drives the channel to the state (or
+ /// throws / cancels on failure). Override to perform a richer probe, e.g. a
+ /// grpc.health.v1.Health/Check RPC returning SERVING.
+ ///
+ public Func>? Probe { get; set; }
+
+ ///
+ /// Human-readable name of the dependency, surfaced in the HealthCheckResult description.
+ ///
+ public string? DependencyName { get; set; }
+
+ /// Maximum time the probe may take before it is treated as unreachable. Default 5 s.
+ public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(5);
+}
diff --git a/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Tests/GrpcDependencyHealthCheckTests.cs b/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Tests/GrpcDependencyHealthCheckTests.cs
new file mode 100644
index 0000000..d1dd117
--- /dev/null
+++ b/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Tests/GrpcDependencyHealthCheckTests.cs
@@ -0,0 +1,72 @@
+using Grpc.Core;
+using Grpc.Net.Client;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using ZB.MOM.WW.Health;
+
+namespace ZB.MOM.WW.Health.Tests;
+
+///
+/// Verifies via an injected probe (no live gRPC server):
+/// probe-true → Healthy, probe-false → Unhealthy, and an from the probe
+/// → Unhealthy. The channel is constructed but never dialled because the probe is stubbed.
+///
+public sealed class GrpcDependencyHealthCheckTests
+{
+ private static readonly GrpcChannel Channel = GrpcChannel.ForAddress("http://localhost");
+
+ private static async Task RunAsync(GrpcDependencyOptions options)
+ {
+ var check = new GrpcDependencyHealthCheck(Channel, options);
+ var context = new HealthCheckContext
+ {
+ Registration = new HealthCheckRegistration("grpc-dep", check, HealthStatus.Unhealthy, tags: null),
+ };
+ return await check.CheckHealthAsync(context, CancellationToken.None);
+ }
+
+ [Fact]
+ public async Task ProbeReturnsTrue_Healthy()
+ {
+ var result = await RunAsync(new GrpcDependencyOptions
+ {
+ Probe = static (_, _) => Task.FromResult(true),
+ });
+
+ Assert.Equal(HealthStatus.Healthy, result.Status);
+ }
+
+ [Fact]
+ public async Task ProbeReturnsFalse_Unhealthy()
+ {
+ var result = await RunAsync(new GrpcDependencyOptions
+ {
+ Probe = static (_, _) => Task.FromResult(false),
+ });
+
+ Assert.Equal(HealthStatus.Unhealthy, result.Status);
+ }
+
+ [Fact]
+ public async Task ProbeThrowsRpcException_Unhealthy()
+ {
+ var result = await RunAsync(new GrpcDependencyOptions
+ {
+ Probe = static (_, _) => throw new RpcException(new Status(StatusCode.Unavailable, "down")),
+ });
+
+ Assert.Equal(HealthStatus.Unhealthy, result.Status);
+ }
+
+ [Fact]
+ public async Task DependencyName_AppearsInDescription()
+ {
+ var result = await RunAsync(new GrpcDependencyOptions
+ {
+ DependencyName = "mxaccessgw worker",
+ Probe = static (_, _) => Task.FromResult(false),
+ });
+
+ Assert.Equal(HealthStatus.Unhealthy, result.Status);
+ Assert.Contains("mxaccessgw worker", result.Description);
+ }
+}