# Resume After Disconnect (/learn/events-store/how-to/resume-after-disconnect)



Events Store subscriptions are **durable by default**. When a subscriber disconnects and reconnects with the same durable name, the store resumes delivery from the last tracked position — no events are missed.

## How Position Tracking Works [#how-position-tracking-works]

<Mermaid
  chart="sequenceDiagram
    participant Sub as Subscriber
    participant ES as Events Store

    Sub->>ES: Subscribe (StartFromFirst, group=processors)
    ES-->>Sub: event seq=1
    ES-->>Sub: event seq=2
    ES-->>Sub: event seq=3
    Note over Sub: Position tracked at seq=3

    Note over Sub,ES: Subscriber disconnects

    Note over ES: Events seq=4, 5, 6 arrive

    Sub->>ES: Reconnect (same group=processors)
    Note over ES: StartPosition ignored — resumes from seq=4
    ES-->>Sub: event seq=4
    ES-->>Sub: event seq=5
    ES-->>Sub: event seq=6"
/>

*The Events Store remembers the last delivered position per durable name; on reconnect it ignores the start position and resumes from where the subscriber left off.*

### Durable Name [#durable-name]

Every Events Store subscription creates a durable name:

```text
DurableName = "{channel}-{group}"
```

* On **first connection**, the `StartPosition` determines where to begin reading
* On **subsequent connections** with the same durable name, the `StartPosition` is **ignored** — delivery resumes from the last tracked position
* To force a fresh replay, use a different `group` name

## Step-by-Step [#step-by-step]

<Steps>
  <Step>
    ### Subscribe with a Durable Group [#subscribe-with-a-durable-group]

    Use a named group to enable durable position tracking.

    <Tabs groupId="language" items="['Go', 'Python', 'Node.js', 'Java', 'C#', 'Kotlin', 'C++', 'Rust', 'Ruby', 'Elixir']">
      <Tab value="Go">
        ```go title="durable_subscriber.go"
        package main

        import (
            "context"
            "fmt"
            "log"

            "github.com/kubemq-io/kubemq-go/v2"
        )

        func main() {
            ctx := context.Background()
            client, err := kubemq.NewClient(ctx,
                kubemq.WithAddress("localhost", 50000),
            )
            if err != nil {
                log.Fatal(err)
            }
            defer client.Close()

            sub, err := client.SubscribeToEventsStore(ctx,
                "orders.events",
                "order-processor",
                kubemq.StartFromFirst(),
                kubemq.WithOnEvent(func(event *kubemq.Event) {
                    fmt.Printf("Processing seq=%d: %s\n",
                        event.Sequence, string(event.Body))
                }),
                kubemq.WithOnError(func(err error) {
                    log.Println("Error:", err)
                }),
            )
            if err != nil {
                log.Fatal(err)
            }
            defer sub.Unsubscribe()

            <-ctx.Done()
        }
        ```
      </Tab>

      <Tab value="Python">
        ```python title="durable_subscriber.py"
        import time
        from kubemq import (
            PubSubClient, EventsStoreSubscription,
            EventStoreStartPosition, CancellationToken,
        )

        def on_event(event):
            print(f"Processing seq={event.sequence}: {event.body.decode('utf-8')}")

        with PubSubClient(address="localhost:50000") as client:
            client.subscribe_to_events_store(
                subscription=EventsStoreSubscription(
                    channel="orders.events",
                    group="order-processor",
                    start_position=EventStoreStartPosition.StartFromFirst,
                    on_receive_event_callback=on_event,
                    on_error_callback=lambda e: print(f"Error: {e}"),
                ),
                cancel=CancellationToken(),
            )
            time.sleep(300)
        ```
      </Tab>

      <Tab value="Node.js">
        ```typescript title="durable_subscriber.ts"
        import { KubeMQClient, EventStoreStartPosition } from 'kubemq-js';

        const client = await KubeMQClient.create({ address: 'localhost:50000' });

        client.subscribeToEventsStore({
          channel: 'orders.events',
          group: 'order-processor',
          startPosition: EventStoreStartPosition.StartFromFirst,
          onEvent: (msg) =>
            console.log(`Processing seq=${msg.sequence}: ${new TextDecoder().decode(msg.body)}`),
          onError: (err) => console.error('Error:', err.message),
        });
        ```
      </Tab>

      <Tab value="Java">
        ```java title="DurableSubscriber.java"
        PubSubClient client = PubSubClient.builder()
            .address("localhost:50000")
            .clientId("order-processor")
            .build();

        client.subscribeToEventsStore(EventsStoreSubscription.builder()
            .channel("orders.events")
            .group("order-processor")
            .startPosition(EventStoreStartPosition.StartFromFirst)
            .onReceiveEventCallback(event ->
                System.out.printf("Processing seq=%d: %s%n",
                    event.getSequence(), new String(event.getBody())))
            .onErrorCallback(err -> System.err.println("Error: " + err.getMessage()))
            .build());

        Thread.sleep(300_000);
        client.close();
        ```
      </Tab>

      <Tab value="C#">
        ```csharp title="DurableSubscriber.cs"
        await using var client = new KubeMQClient(new KubeMQClientOptions());
        await client.ConnectAsync();

        await foreach (var msg in client.SubscribeToEventsStoreAsync(
            new EventsStoreSubscription
            {
                Channel = "orders.events",
                Group = "order-processor",
                StartPosition = EventStoreStartPosition.StartFromFirst,
            }))
        {
            Console.WriteLine($"Processing seq={msg.Sequence}: "
                + $"{Encoding.UTF8.GetString(msg.Body.Span)}");
        }
        ```
      </Tab>

      <Tab value="Kotlin">
        ```kotlin title="DurableSubscriber.kt"
        val client = KubeMQClient.pubSub {
            address = "localhost:50000"
            clientId = "order-processor"
        }

        client.use {
            client.subscribeToEventsStore {
                channel = "orders.events"
                group = "order-processor"
                startPosition = StartPosition.StartFromFirst
            }.collect { msg ->
                println("Processing seq=${msg.sequence}: ${String(msg.body)}")
            }
        }
        ```
      </Tab>

      <Tab value="C++">
        ```cpp title="durable_subscriber.cc"
        kubemq::ClientOptions options;
        options.set_address("localhost", 50000);
        options.set_client_id("order-processor");
        auto client = kubemq::Client::Create(options).value();

        client->SubscribeToEventsStore("orders.events", "order-processor",
            kubemq::StartPosition::StartFromFirst,
            [](const kubemq::EventStoreReceived& msg) {
                std::cout << "Processing seq=" << msg.sequence()
                          << ": " << msg.body() << std::endl;
            },
            [](const std::string& err) {
                std::cerr << "Error: " << err << std::endl;
            });
        ```
      </Tab>

      <Tab value="Rust">
        ```rust title="durable_subscriber.rs"
        use kubemq::prelude::*;
        use kubemq::EventsStoreSubscription;
        use std::time::Duration;

        #[tokio::main]
        async fn main() -> kubemq::Result<()> {
            let client = KubemqClient::builder()
                .host("localhost")
                .port(50000)
                .build()
                .await?;

            // Named group enables durable position tracking.
            let sub = client
                .subscribe_to_events_store(
                    "orders.events",
                    "order-processor",
                    EventsStoreSubscription::StartFromFirst,
                    |event| {
                        Box::pin(async move {
                            println!(
                                "Processing seq={}: {}",
                                event.sequence,
                                String::from_utf8_lossy(&event.body)
                            );
                        })
                    },
                    None,
                )
                .await?;

            tokio::time::sleep(Duration::from_secs(300)).await;
            sub.unsubscribe().await;
            client.close().await?;
            Ok(())
        }
        ```
      </Tab>

      <Tab value="Ruby">
        ```ruby title="durable_subscriber.rb"
        require 'kubemq'

        client = KubeMQ::PubSubClient.new(address: 'localhost:50000', client_id: 'order-processor')
        cancel = KubeMQ::CancellationToken.new

        # Named group enables durable position tracking.
        subscription = KubeMQ::PubSub::EventsStoreSubscription.new(
          channel: 'orders.events',
          group: 'order-processor',
          start_position: KubeMQ::PubSub::EventStoreStartPosition::START_FROM_FIRST
        )

        client.subscribe_to_events_store(subscription, cancellation_token: cancel, on_error: lambda { |e|
          puts "Error: #{e.message}"
        }) do |event|
          puts "Processing seq=#{event.sequence}: #{event.body}"
        end

        sleep 300
        cancel.cancel
        client.close
        ```
      </Tab>

      <Tab value="Elixir">
        ```elixir title="durable_subscriber.exs"
        {:ok, client} =
          KubeMQ.Client.start_link(address: "localhost:50000", client_id: "order-processor")

        # Named group enables durable position tracking.
        {:ok, _sub} =
          KubeMQ.Client.subscribe_to_events_store(client, "orders.events",
            start_at: :start_from_first,
            group: "order-processor",
            on_event: fn event ->
              IO.puts("Processing seq=#{event.sequence}: #{event.body}")
            end,
            on_error: fn err -> IO.puts("Error: #{inspect(err)}") end
          )

        Process.sleep(300_000)
        KubeMQ.Client.close(client)
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ### Disconnect and Reconnect [#disconnect-and-reconnect]

    1. Run the subscriber and let it process events up to seq=10
    2. Stop the subscriber (Ctrl+C)
    3. Publish more events (seq 11-15)
    4. Restart the subscriber with the **same group name**

    **Result:** The subscriber picks up at seq=11, not seq=1. The `StartFromFirst` parameter is ignored on reconnection because the durable position already exists.
  </Step>

  <Step>
    ### Force a Fresh Replay [#force-a-fresh-replay]

    To replay from the beginning again, use a **different group name**:

    ```bash
    # Original group — resumes from last position
    GROUP=order-processor

    # New group — replays from StartFromFirst
    GROUP=order-processor-v2
    ```

    The old group's position data remains until the channel is purged or the `MaxPurgeInactive` timeout expires.
  </Step>
</Steps>

## Position Tracking Details [#position-tracking-details]

| Behavior             | Detail                                              |
| -------------------- | --------------------------------------------------- |
| Tracking granularity | Per durable name (`{channel}-{group}`)              |
| Position persistence | Stored alongside the event data on disk             |
| First connection     | Uses the `StartPosition` you specify                |
| Reconnection         | Ignores `StartPosition`, resumes from last position |
| No group specified   | Empty string group still creates a durable name     |
| Multiple groups      | Each group tracks position independently            |

<Callout type="warn">
  Even with an empty `group` parameter, Events Store creates a durable subscription. The durable name becomes `{channel}-`. To create a truly ephemeral subscription, use plain [Events](/learn/events) instead.
</Callout>

## Related [#related]

* [Consumer Groups](/learn/events-store/tutorials/consumer-groups) for distributed processing
* [Replay Events](/learn/events-store/tutorials/replay-events) for all 6 start positions
* [Events Store Reference](/learn/events-store/reference) for subscription parameters
