# Understanding the wacli Bootstrap Sync Process After Authentication

> Explore the wacli bootstrap sync process after authentication. Learn how it imports contacts, syncs history, stores messages, and self-exits.

- Repository: [Peter Steinberger/wacli](https://github.com/steipete/wacli)
- Tags: deep-dive
- Published: 2026-04-17

---

**The bootstrap sync process in wacli is a post-authentication routine that imports contacts and groups, consumes the full WhatsApp history sync, stores live messages, and automatically exits after a configurable idle period.**

When you authenticate with `wacli auth`, the tool immediately executes a **bootstrap sync process** that prepares your local SQLite database for WhatsApp operations. This routine, implemented in [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go) and [`internal/app/bootstrap.go`](https://github.com/steipete/wacli/blob/main/internal/app/bootstrap.go), ensures you have a complete local copy of your contacts, groups, and message history before the process terminates.

## What Triggers the Bootstrap Sync Process?

The bootstrap sync activates in two scenarios:

1. **CLI authentication**: Running `wacli auth` creates an `App` instance and invokes `Sync` with `SyncModeBootstrap` (see [`cmd/wacli/auth.go`](https://github.com/steipete/wacli/blob/main/cmd/wacli/auth.go), lines 33-45)
2. **Programmatic initialization**: Calling `App.Sync(context.Background(), app.SyncOptions{Mode: app.SyncModeBootstrap, ...})` directly

Unlike continuous sync modes, the bootstrap process imports all historical data and then exits after a period of inactivity, making it ideal for initial setup or one-time backups.

## Step-by-Step Breakdown of the Bootstrap Sync

The `Sync` function in [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go) orchestrates a 10-step sequence:

### 1. Initialize the WhatsApp Client

The process calls `a.OpenWA()` to ensure the `whatsmeow` client is ready, then `a.Connect` establishes the WebSocket connection. If no cached credentials exist, the system displays a QR code for authentication (controlled by the `AllowQR` option).

### 2. Start Optional Media Workers

If `DownloadMedia` is enabled in `SyncOptions`, the sync routine initializes a pool of background workers to fetch media files while message processing continues (see [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go), lines 57-64).

### 3. Import Contacts

The `refreshContacts` function in [`internal/app/bootstrap.go`](https://github.com/steipete/wacli/blob/main/internal/app/bootstrap.go) (lines 8-26) retrieves all contacts via `wa.GetAllContacts`, then persists each one to the SQLite database using `db.UpsertContact`. This runs by default during bootstrap (`opts.RefreshContacts` is true).

### 4. Import Groups

Similarly, `refreshGroups` in [`internal/app/bootstrap.go`](https://github.com/steipete/wacli/blob/main/internal/app/bootstrap.go) (lines 29-45) fetches joined groups via `wa.GetJoinedGroups` and stores them using `db.UpsertGroup` and `db.UpsertChat`. This ensures group metadata exists before historical messages arrive.

### 5. Consume History Sync Events

After connection, WhatsApp emits a `*events.HistorySync` containing all conversations and historic messages. The event handler in [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go) (lines 15-40 of the handler) iterates through every message, constructs a `ParsedMessage`, and stores it via `storeParsedMessage`.

### 6. Process Live Messages

While the bootstrap connection remains active, new incoming messages are handled identically to historic ones. They are parsed, stored, and optionally queued for media download (see [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go), lines 94-114).

### 7. Idle Exit Mechanism

Bootstrap mode terminates automatically rather than running forever. A ticker monitors the timestamp of the last processed event; if the client remains idle for the duration specified by `IdleExit` (default 30 seconds), the process prints a notice and returns. This logic resides in [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go) (lines 94-118).

### 8. Return Results

The `Sync` function concludes by returning a `SyncResult` struct containing the count of messages stored during the run (see [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go), lines 44-45).

## Practical Usage Examples

### Authenticate with Full Bootstrap Sync

Run the following to authenticate, import contacts and groups, download media, and exit after 30 seconds of inactivity:

```bash
wacli auth --download-media

```

Under the hood, this executes `Sync` with `SyncModeBootstrap` as implemented in [`cmd/wacli/auth.go`](https://github.com/steipete/wacli/blob/main/cmd/wacli/auth.go) (lines 33-45).

### Configure Custom Idle Timeout

Extend the idle period to 2 minutes before automatic exit:

```bash
wacli auth --idle-exit=2m

```

This maps to `SyncOptions.IdleExit` in [`cmd/wacli/auth.go`](https://github.com/steipete/wacli/blob/main/cmd/wacli/auth.go) (lines 68-70) and is honored by the idle-exit logic in [`sync.go`](https://github.com/steipete/wacli/blob/main/sync.go).

### Programmatic Bootstrap in Go

Embed the bootstrap sync in your own application:

```go
package main

import (
	"context"
	"time"

	"github.com/steipete/wacli/internal/app"
)

func main() {
	// Initialize App with configuration
	a, _ := app.NewApp(/* config */)

	// Execute bootstrap sync
	res, err := a.Sync(context.Background(), app.SyncOptions{
		Mode:            app.SyncModeBootstrap,
		AllowQR:         true,
		RefreshContacts: true,
		RefreshGroups:   true,
		DownloadMedia:   true,
		IdleExit:        30 * time.Second,
	})
	if err != nil {
		panic(err)
	}
	println("Messages stored:", res.MessagesStored)
}

```

This executes the full 10-step bootstrap sequence defined in [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go).

## Key Source Files and Architecture

Understanding the bootstrap sync requires familiarity with these implementation files:

- **[`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go)** – Core synchronization engine containing the main `Sync` function, connection handling, event processing, and idle-exit timer (lines 94-118).
- **[`internal/app/bootstrap.go`](https://github.com/steipete/wacli/blob/main/internal/app/bootstrap.go)** – Helper functions `refreshContacts` (lines 8-26) and `refreshGroups` (lines 29-45) that import address book data before history synchronization.
- **[`cmd/wacli/auth.go`](https://github.com/steipete/wacli/blob/main/cmd/wacli/auth.go)** – CLI entry point that parses `--download-media` and `--idle-exit` flags, then invokes `Sync` with `SyncModeBootstrap` (lines 33-45, 68-70).
- **`internal/store/`** – SQLite persistence layer implementing `UpsertContact`, `UpsertGroup`, and `UpsertMessage` used throughout the bootstrap steps.
- **`internal/wa/`** – Thin wrapper around `whatsmeow` providing `GetAllContacts`, `GetJoinedGroups`, and `ParseHistoryMessage` for the bootstrap import routines.

## Summary

- The **bootstrap sync process** initializes your local WhatsApp database immediately after authentication via `wacli auth`.
- It executes a **10-step sequence**: connection setup, media worker initialization, contact import, group import, history sync consumption, live message handling, and idle-exit termination.
- Key entry points are the CLI command `wacli auth` and the programmatic API `App.Sync` with `SyncModeBootstrap`.
- The process automatically terminates after a configurable **idle period** (default 30 seconds), unlike continuous sync modes.
- Implementation spans [`internal/app/sync.go`](https://github.com/steipete/wacli/blob/main/internal/app/sync.go), [`internal/app/bootstrap.go`](https://github.com/steipete/wacli/blob/main/internal/app/bootstrap.go), and [`cmd/wacli/auth.go`](https://github.com/steipete/wacli/blob/main/cmd/wacli/auth.go).

## Frequently Asked Questions

### How long does the bootstrap sync process take to complete?

The duration depends on your WhatsApp account size. Importing contacts and groups completes in seconds, but history sync duration varies with message volume. The process automatically exits 30 seconds after the last activity by default, or you can configure this with the `--idle-exit` flag.

### What is the difference between bootstrap sync and continuous sync?

Bootstrap sync (`SyncModeBootstrap`) imports all historical data and exits after an idle period, making it ideal for initial setup or one-time backups. Continuous sync maintains a persistent connection, processing live messages indefinitely without automatic termination.

### Why are contacts and groups imported before the history sync?

The `refreshContacts` and `refreshGroups` functions in [`internal/app/bootstrap.go`](https://github.com/steipete/wacli/blob/main/internal/app/bootstrap.go) populate the database with metadata needed to properly associate historical messages with their senders and group contexts. This ensures that when `HistorySync` events arrive, the foreign key relationships in the SQLite database remain consistent.

### Can I skip media download during bootstrap sync?

Yes. The `--download-media` flag is optional. When omitted, the bootstrap sync imports text messages, contacts, and groups without fetching media files. You can enable media download later by running `wacli sync` with the appropriate flags.