> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackshift.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# State Store

> Use durable state for progress, deduplication, counters, TTL-backed markers, and locks.

<Tip>
  **Live.** This area is documented as current, user-reliable behavior.
</Tip>

## Goal

Store small pieces of durable job memory without adding a separate database table for every workflow concern.

## Prerequisites

* A job handler with access to state

## Workflow

<Steps>
  <Step>
    Read state when a job starts or resumes.
  </Step>

  <Step>
    Set state after meaningful progress.
  </Step>

  <Step>
    Use TTLs for temporary markers.
  </Step>

  <Step>
    Use counters for progress and rate-aware flows.
  </Step>

  <Step>
    Use locks when only one run should own a resource at a time.
  </Step>
</Steps>

## Get, set, and delete

```ts Track progress theme={null}
const progressKey = `imports:${payload.importId}:progress`

await state.set(progressKey, { processed: 100, total: 500 })

const progress = await state.get<{ processed: number; total: number }>(progressKey)

if (progress.processed === progress.total) {
  await state.delete(progressKey)
}
```

## TTL

Use TTL-backed state for temporary dedupe markers, short-lived locks, and status values that should expire automatically.

```ts Temporary webhook marker theme={null}
await state.set('webhooks:stripe:evt_123', { processed: true }, {
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
})
```

## Counters and locks

* Use counters for import progress, retry-aware limits, and lightweight metrics.
* Use locks for work that should have one owner, such as rebuilding a customer search index or provisioning a shared resource.
* Give locks a short TTL so abandoned work can recover.

```ts Count processed rows theme={null}
const processed = await state.increment(`imports:${payload.importId}:processed`)

if (processed % 100 === 0) {
  await publishProgress(payload.importId, processed)
}
```

## Webhook deduplication

```ts Dedupe with state and idempotency theme={null}
await stackshift.queue('webhooks').enqueue(
  'processStripeWebhook',
  { eventId: event.id, type: event.type },
  { idempotencyKey: `stripe:${event.id}` }
)
```

## Expected result

<Check>
  Workflow progress and deduplication survive retries and process restarts.
</Check>

## Related guides

<CardGroup cols={2}>
  <Card title="Idempotency" href="/durable-jobs/idempotency">
    Use idempotency keys so duplicate requests do not create duplicate work.
  </Card>

  <Card title="Observability" href="/durable-jobs/observability">
    Inspect each job run through status, attempts, logs, payload, result, errors, and timeline.
  </Card>
</CardGroup>
