---
title: Events and Event Sinks
description: Runtime event model and how DurableStack publishes events to sinks.
order: 42
available_in: [dotnet, nodejs]
---

DurableStack emits lifecycle and worker events during execution.

Event sinks consume those events for logging, telemetry, and hosted observability ingestion.

## Event types

- `job_claimed`
- `job_started`
- `job_succeeded`
- `job_failed`
- `job_retried`
- `retry_scheduled`
- `worker_heartbeat`

## How sinks are wired

- DurableStack always has a no-op sink by default.
- You can add logging sink with `UseDurableStackLoggingEventSink()`.
- Hosted ingestion sink is added automatically when both tenant credentials are configured.

## Hosted ingestion behavior

- Event sink is enabled only when tenant credentials are configured.
- Events are buffered in a bounded channel.
- Sync service flushes in batches with retry/backoff.

## Example

:::code-group
```csharp
using DurableStack.Hosting.DependencyInjection;

builder.Services.AddDurableStackPostgres(connectionString, options =>
{
    options.Eventing.TenantId = "tenant_...";
    options.Eventing.ClientSecret = "secret_...";
});

builder.Services.UseDurableStackLoggingEventSink();
```

```typescript
import {
  createDurableStackPostgres,
  type DurableStackEventSink,
  EVENT_TYPES
} from "durablestack";

const loggingSink: DurableStackEventSink = {
  publish: async (evt) => {
    if (evt.eventType === EVENT_TYPES.JOB_FAILED) {
      console.error("job failed", evt);
    }
  }
};

const { runtime } = await createDurableStackPostgres(
  { connectionString: process.env.DATABASE_URL! },
  {
    eventing: {
      tenantId: "tenant_...",
      clientSecret: "secret_..."
    }
  },
  [loggingSink]
);
```
:::

## Recommended practice

- Configure tenant identity and client secret explicitly.
- Keep environment/service labels consistent.
- Use correlation IDs for cross-system traceability.
