dbsync2

command module
v1.21.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

README

Sync Client V2

The Sync Client (dbsync2) can be used to transfer all updates on contacts to an SQL database or a web service.

Contents

Activation

To be able to use the Sync Client, activate the corresponding connector in the campaign settings.
After successful activation, a token is displayed below the connectors. It is required to use the client.
The token is only visible to the user who activated the connector.

how to get dbsycn2 token in Dialfire

Quickstart: PostgreSQL with the prebuilt binary

You do not need Go, Docker, or the source code to run dbsync2. Downloading the binary is enough.

1. Download the binary

Open the downloads page and pick the file with the highest version number for your platform:

Platform File name
Linux (x86_64) dbsync2_<VERSION>
Windows (x86_64) dbsync2_<VERSION>.exe
macOS (Intel) dbsync2_<VERSION>_mac
macOS (Apple Silicon) dbsync2_<VERSION>_mac_arm64
# Linux — replace 1.20.2 with the highest version on the downloads page
curl -L -o dbsync2 https://bitbucket.org/modima/dbsync2/downloads/dbsync2_1.20.2
chmod +x dbsync2
./dbsync2 --version
2. Create the target database

dbsync2 creates its tables, but it does not create the database — that must already exist. Installing PostgreSQL is not sufficient.

sudo -u postgres psql <<'SQL'
CREATE USER dbsync WITH PASSWORD 'my_password';
CREATE DATABASE my_database OWNER dbsync;
SQL

Note the database name, user, and password — all three go into the connection URL.

3. Import the existing data (once)

db_init fetches the complete campaign history and then exits. Depending on the campaign size this can take a while.

./dbsync2 --a db_init \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'postgres://dbsync:my_password@localhost:5432/my_database?sslmode=disable'
4. Keep the database up to date (continuously)

db_sync runs until it is stopped and picks up where the previous run left off.

./dbsync2 --a db_sync \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'postgres://dbsync:my_password@localhost:5432/my_database?sslmode=disable'

Add --v to see the log on stdout instead of writing it to a log file.

Things that are easy to get wrong
  • --a (mode) is required. Without it the client aborts; there is no default mode.
  • Everything is a flag. There are no positional arguments — the connection URL must be passed as --url '...', not appended to the command line.
  • Quote the URL. A URL containing ?, &, or special characters in the password must be single-quoted in the shell.
  • Use the right port. PostgreSQL 5432, MySQL/MariaDB 3306, SQL Server 1433.
  • The database must exist before the first run (see step 2).
  • --s (start date) is optional. Omit it and db_init fetches everything, db_sync resumes from its saved state, and db_update starts one week ago.
  • --fm / --fp (filters) are optional. Omit them to sync everything.

Installation

Download the latest version of the binary
Run via Docker
docker run --rm dialfire/dbsync2:latest dbsync2 --version

See Docker Compose Deployment below for a full setup.

Build from source

dbsync2 is a Go module, so no GOPATH setup and no godep are needed. Clone the repository and build it in place:

sudo apt-get install git golang-go     # Go 1.23 or newer is required
git clone https://bitbucket.org/modima/dbsync2.git
cd dbsync2
go build -o dbsync2
./dbsync2 --version

Cross-compiling for other platforms from the same checkout:

# Linux (static, as used for the released binary)
CGO_ENABLED=0 GOOS=linux go build -o dbsync2

# Windows
GOOS=windows GOARCH=amd64 go build -o dbsync2.exe

# macOS (Intel / Apple Silicon)
GOOS=darwin GOARCH=amd64 go build -o dbsync2_mac
GOOS=darwin GOARCH=arm64 go build -o dbsync2_mac_arm64

go get and go install do not work for this repository. go get bitbucket.org/modima/dbsync2 fails with "go.mod file not found in current directory or any parent directory" — since Go 1.17 go get only works inside an existing module and no longer installs commands. go install bitbucket.org/modima/dbsync2@latest does not resolve either, because the repository is not published with v-prefixed semver tags via a module proxy. Use git clone + go build as shown above, or simply download the prebuilt binary.

Troubleshooting

Message Cause Fix
Execution mode (--a) is required. Valid modes: ... --a was omitted Add --a db_init, --a db_sync, --a db_update, or --a webhook
Invalid mode 'xyz' Typo in the mode name Use one of webhook, db_init, db_update, db_sync
Error parsing command line: unknown flag: --ss Typo in a flag name — parsing stops there, so all following flags (e.g. --url) are ignored Correct the flag name (--s is the start date); run dbsync2 --help for the full list
Unexpected argument 'postgres://...' The URL was passed positionally Pass it as --url 'postgres://...'
database connection URL (--url) is required in mode 'db_sync' --url missing, or dropped because of a typo in an earlier flag Add --url '...' and check the other flag names
unsupported database driver 'oracle' Unsupported or misspelled URL scheme Use postgres://, mysql://, or sqlserver://
Database name is missing from --url ... URL ends after the host/port Append /my_database
dial tcp 127.0.0.1:1433: connect: connection refused Wrong port or server not running PostgreSQL 5432, MySQL 3306, SQL Server 1433
FATAL: database "my_database" does not exist The database was never created See step 2 of the Quickstart
403 Forbidden from the API Wrong campaign ID or token, or the connector is not activated Re-copy the token from the campaign settings (Activation)
pq: prepared statement does not exist (PgBouncer) Version older than v1.18 Upgrade, or set PgBouncer to pool_mode=session — see PgBouncer Compatibility
Error 1118: Row size too large ... is 8126 (MySQL) The campaign has too many fields for one InnoDB row — see Campaigns with many fields Restrict the columns with --cf / --cfx, then drop the columns you do not need
Error 1193: Unknown system variable 'useSSL' (MySQL) JDBC parameter in the URL; the Go driver forwards unknown parameters to the server Use ?tls=false instead of ?useSSL=false

Note that postgresql:// is accepted as an alias for postgres:// (likewise mariadb:// for mysql:// and mssql:// for sqlserver://) as of v1.20.2. Earlier versions only accept the canonical scheme names.

Local Release Flow

For local releases (no CI), use the helper scripts:

./preflight.sh
./release.sh

preflight.sh checks that Docker is running, the working tree is clean, and the version tag does not already exist.
release.sh runs the build, tags the repo, pushes the Docker image, and appends the digest to RELEASES.md.

To verify a released image:

./verify-image.sh

To run preflight for an existing tag (e.g., current release), set:

ALLOW_EXISTING_TAG=1 ./preflight.sh

Docker Compose Deployment

For new users and those who prefer containerized deployment, we provide a pre-configured docker-compose.yml that supports all modes (PostgreSQL, MySQL/MariaDB, SQL Server, and Webhook). With Docker Compose you can run dbsync2 without building or modifying any source code.

Prerequisites
Configuration

Create a .env file in the same directory as your docker-compose.yml with the following content (adjust the values as needed):

# Campaign settings
CAMPAIGN_ID=my_campaign_id
CAMPAIGN_TOKEN=my_campaign_token

# PostgreSQL settings
POSTGRES_DB=mydb
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword

# MySQL settings
MYSQL_DB=mydb
MYSQL_USER=myuser
MYSQL_PASSWORD=mypassword
MYSQL_ROOT_PASSWORD=myrootpassword

# SQL Server settings
SA_PASSWORD=YourStrong!Passw0rd
SQLSERVER_DB=mydb

# Webhook settings
WEBHOOK_URL=https://example.com/api/transactions/
docker-compose.yml

Below is a sample docker-compose.yml file that uses your Docker Hub image (dialfire/dbsync2:latest) and dynamically reads all connection details from the .env file:

version: "3.8"

services:
  #############################
  # PostgreSQL Example
  #############################
  postgres:
    image: postgres:16
    container_name: postgres_sync
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-mydb}
      POSTGRES_USER: ${POSTGRES_USER:-myuser}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mypassword}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  dbsync2_postgres:
    image: dialfire/dbsync2:latest
    container_name: dbsync2_postgres
    restart: unless-stopped
    depends_on:
      - postgres
    environment:
      CAMPAIGN_ID: ${CAMPAIGN_ID}
      CAMPAIGN_TOKEN: ${CAMPAIGN_TOKEN}
      POSTGRES_USER: ${POSTGRES_USER:-myuser}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mypassword}
      POSTGRES_DB: ${POSTGRES_DB:-mydb}
    command: >
      dbsync2 --a db_sync --c ${CAMPAIGN_ID} --ct ${CAMPAIGN_TOKEN} --url postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable

  #############################
  # MySQL/MariaDB Example
  #############################
  mysql:
    image: mysql:8
    container_name: mysql_sync
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${MYSQL_DB:-mydb}
      MYSQL_USER: ${MYSQL_USER:-myuser}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-mypassword}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
    volumes:
      - ./data/mysql:/var/lib/mysql
    ports:
      - "3306:3306"

  dbsync2_mysql:
    image: dialfire/dbsync2:latest
    container_name: dbsync2_mysql
    restart: unless-stopped
    depends_on:
      - mysql
    environment:
      CAMPAIGN_ID: ${CAMPAIGN_ID}
      CAMPAIGN_TOKEN: ${CAMPAIGN_TOKEN}
      MYSQL_USER: ${MYSQL_USER:-myuser}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-mypassword}
      MYSQL_DB: ${MYSQL_DB:-mydb}
    command: >
      dbsync2 --a db_sync --c ${CAMPAIGN_ID} --ct ${CAMPAIGN_TOKEN} --url mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DB}?tls=false

  #############################
  # SQL Server Example
  #############################
  sqlserver:
    image: mcr.microsoft.com/mssql/server:2019-latest
    container_name: sqlserver_sync
    restart: unless-stopped
    environment:
      SA_PASSWORD: ${SA_PASSWORD:-YourStrong!Passw0rd}
      ACCEPT_EULA: "Y"
      MSSQL_PID: "Developer"
    ports:
      - "1433:1433"

  dbsync2_sqlserver:
    image: dialfire/dbsync2:latest
    container_name: dbsync2_sqlserver
    restart: unless-stopped
    depends_on:
      - sqlserver
    environment:
      CAMPAIGN_ID: ${CAMPAIGN_ID}
      CAMPAIGN_TOKEN: ${CAMPAIGN_TOKEN}
      SA_PASSWORD: ${SA_PASSWORD:-YourStrong!Passw0rd}
      SQLSERVER_DB: ${SQLSERVER_DB:-mydb}
    command: >
      dbsync2 --a db_sync --c ${CAMPAIGN_ID} --ct ${CAMPAIGN_TOKEN} --url sqlserver://sa:${SA_PASSWORD}@sqlserver:1433/${SQLSERVER_DB}

  #############################
  # Webhook Example
  #############################
  dbsync2_webhook:
    image: dialfire/dbsync2:latest
    container_name: dbsync2_webhook
    restart: unless-stopped
    environment:
      CAMPAIGN_ID: ${CAMPAIGN_ID}
      CAMPAIGN_TOKEN: ${CAMPAIGN_TOKEN}
      WEBHOOK_URL: ${WEBHOOK_URL:-https://example.com/api/transactions/}
    command: >
      dbsync2 --a webhook --c ${CAMPAIGN_ID} --ct ${CAMPAIGN_TOKEN} --url ${WEBHOOK_URL}

networks:
  default: {}
Running via Docker Compose

Once your .env file is configured and the docker-compose.yml file is in place, simply run:

docker-compose up -d

Docker Compose will pull the pre-built dialfire/dbsync2:latest image and start the services with your specified configuration.

How It Works

All updates on contacts are loaded every minute and then transferred directly to the web service or database.

Usage

Required Options

Every invocation needs at least these four flags:

Option Description
--a Execution mode: webhook, db_init, db_update or db_sync (see below)
--c Campaign ID
--ct Campaign token (see Activation)
--url Target: database connection URL, or webhook URL in mode webhook

All other options are optional. All flags can also be set via environment variables prefixed with DBSYNC_ in upper case (e.g. DBSYNC_CT=... for --ct); an explicitly passed flag always wins over the environment variable. This requires v1.21.0 or newer — in earlier versions the DBSYNC_* variables were silently ignored.

Execution Modes (--a)
Mode Behaviour When to use it
db_init Writes the complete campaign history to the database, then exits. Once, for the initial setup.
db_sync Writes all new updates continuously and does not exit. Resumes from the last processed timestamp. Normal production operation, after db_init.
db_update Writes all updates since --s (default: one week ago), then exits. One-off catch-up after downtime or to backfill a date range.
webhook Sends every transaction and inbound call to a web service and does not exit. Integration with an external service instead of a database.

The typical sequence for a new database is db_init once, then db_sync permanently.

db_init and db_update do the same work and differ only in their default start date: db_init without --s fetches everything, db_update without --s starts one week ago. Passing --s to db_init limits it to that date just like db_update does.

SQL Database

The client currently supports the following database systems:

  • MySQL / MariaDB
  • PostgreSQL
  • Microsoft SQL Server

The database itself must be created before the first run — dbsync2 creates only the tables inside an existing database, it does not issue CREATE DATABASE. See step 2 of the Quickstart.

The client creates the following tables within that database:

  • contacts - Contains all $ fields, as well as the custom fields of the campaign.
  • transactions - Contains all transactions and the reference contact_id to the corresponding contact.
  • connections - Contains all connections of the transaction and the reference transaction_id to the corresponding transaction.
  • recordings - Contains all call recordings of the connection and the reference connection_id to the corresponding connection.
  • inbound_calls - Contains all inbound calls and the reference contact_id to the corresponding contact.

The *_id columns are references by convention — no FOREIGN KEY constraints and no indexes on them are created. See Caveats and Operational Notes.

dbsync2 sql database table structure

contacts
Column Type Description
$id varchar(50) Primary key - unique contact identifier
$md5 varchar(50) MD5 hash of contact data (used for change detection)
$ref varchar(50) External reference ID
$version varchar(50) Contact version
$campaign_id varchar(50) Campaign identifier
$task_id varchar(50) Current task identifier
$task varchar(50) Current task name
$status varchar(50) Contact status
$status_detail varchar(100) Detailed status information
$created_date varchar(50) Creation date
$entry_date varchar(50) Entry date into the campaign
$owner varchar(50) Owner/agent assigned
$follow_up_date varchar(50) Scheduled follow-up date
$phone varchar(50) Phone number
$timezone varchar(50) Contact timezone
$caller_id varchar(50) Caller ID used for outbound calls
$source varchar(50) Contact source
$comment text Comments/notes
$error varchar(50) Last error message
$recording varchar(100) Recording filename
$recording_url varchar(100) Recording URL
$changed varchar(50) Last modification timestamp
(custom fields) text / numeric One column per campaign field, added automatically as fields are added to the campaign
transactions
Column Type Description
id varchar(50) Primary key - unique transaction identifier
contact_id varchar(50) Foreign key → contacts.$id
task_id varchar(50) Task identifier
task varchar(50) Task name
status varchar(50) Transaction status
status_detail varchar(100) Detailed status information
fired varchar(50) Timestamp when transaction was fired
pause_time_sec numeric Pause time in seconds
edit_time_sec numeric Edit time in seconds
wrapup_time_sec numeric Wrap-up time in seconds
wait_time_sec numeric Wait time in seconds
user varchar(50) User/agent ID
user_loginName varchar(50) User login name
user_branch varchar(50) User branch
user_tenantAlias varchar(50) User tenant alias
actor varchar(50) Actor type (user, system, etc.)
type varchar(50) Transaction type
result varchar(50) Transaction result
trigger varchar(50) What triggered the transaction
isHI boolean Human interaction flag
revoked boolean Whether transaction was revoked
$changed varchar(50) Last modification timestamp
connections
Column Type Description
id varchar(50) Primary key - unique connection identifier
parent_connection_id varchar(50) Foreign key → connections.id (for transfers)
transfer_target_address varchar(50) Transfer target address
call_uuid varchar(50) Call UUID
global_call_uuid varchar(100) Global call UUID
transaction_id varchar(50) Foreign key → transactions.id
task_id varchar(50) Task identifier
contact_id varchar(50) Foreign key → contacts.$id
phone varchar(50) Phone number dialed
user varchar(50) User/agent ID
actor varchar(50) Actor type
hangup_party varchar(50) Who hung up (customer, agent, system)
isThirdPartyConnection varchar(50) Third-party connection flag
dialerdomain varchar(50) Dialer domain
fired varchar(50) Timestamp when connection was initiated
started varchar(50) Call start time
initiated varchar(50) Call initiation time
connected varchar(50) Call connection time
disconnected varchar(50) Call disconnection time
ended varchar(50) Call end time
duration numeric Call duration in seconds
remote_number varchar(50) Remote party phone number
line_number varchar(50) Line phone number
technology varchar(50) Connection technology
result varchar(50) Connection result
code numeric Result code
call_initiated varchar(50) Call initiated timestamp
call_connected varchar(50) Call connected timestamp
call_disconnected varchar(50) Call disconnected timestamp
$changed varchar(50) Last modification timestamp
recordings
Column Type Description
id varchar(50) Primary key - unique recording identifier
contact_id varchar(50) Foreign key → contacts.$id
connection_id varchar(50) Foreign key → connections.id
started varchar(50) Recording start time
stopped varchar(50) Recording stop time
filename varchar(100) Recording filename
location varchar(100) Recording storage location/URL
$changed varchar(50) Last modification timestamp
inbound_calls
Column Type Description
id varchar(50) Primary key - unique inbound call identifier (call_id)
line_id varchar(50) Line identifier
campaign_id varchar(50) Campaign identifier
task_id varchar(50) Task identifier
task_name varchar(50) Task name
contact_id varchar(50) Foreign key → contacts.$id (if associated)
remote_number varchar(50) Caller's phone number
line_number varchar(50) Called phone number
started varchar(50) Call start time
connected varchar(50) Agent connection time
disconnected varchar(50) Call end time
user varchar(50) Agent ID who handled the call
state varchar(50) Call state (open, talking, done, handled_by_ivr, lost)
connectable_time varchar(50) When call became connectable
disposition varchar(50) Call termination type (normal_clearing, ooo, handled_by_ivr, transferred, rejected, error)
dispatch_error boolean True if connect_time set but was_connected is false
initial_line_id varchar(50) First line ID before IVR routing
initial_ivr_id varchar(50) First IVR script ID
ivr_id varchar(50) Last/effective IVR script ID
type varchar(50) Classification type (default, inbound, rebound)
hangup_party varchar(50) Who hung up (customer, agent, system)
$changed varchar(50) Last modification timestamp
Database Connection URL Schema

The connection URL is passed with --url and should be single-quoted in the shell. Mind the default port of your DBMS — using the wrong one results in connection refused.

MySQL / MariaDB (default port 3306):
mysql://username:password@localhost:3306/database?tls=false
PostgreSQL (default port 5432):
postgres://username:password@localhost:5432/database?sslmode=disable
Microsoft SQL Server (default port 1433):
sqlserver://username:password@localhost:1433/database
sqlserver://username:password@localhost:1433/instance/database   # named instance

Since v1.20.2 the aliases postgresql://, mariadb:// and mssql:// are accepted as well. Earlier versions only accept the canonical scheme names above and abort with Invalid database driver 'postgresql'.

To place the tables into a specific PostgreSQL schema, pass the search path as a connection option. The schema must already exist (CREATE SCHEMA my_schema;) and %3D is the URL-encoded =:

--url 'postgres://my_user:my_password@localhost:5432/my_database?sslmode=disable&options=-csearch_path%3Dmy_schema'
Web Service

As an alternative to a database, the transactions and inbound calls can be forwarded to a web service. The service must accept POST requests and reply with a status code between 200 and 299 upon success. Otherwise, the data will be resent (up to 10 attempts).

Transaction Payload

Transactions are sent with the following JSON format:

{
    "contact": {...},
    "transaction": {...},
    "state": "new" | "updated"
}
  • contact - Contains the contact details.
  • transaction - Contains the corresponding transaction.
  • state - new for a new transaction, updated when the transaction is updated (e.g., when connection data is added later).
Inbound Call Payload

Inbound calls are sent with the following JSON format:

With contact (when the inbound call has a contact_id):

{
    "contact": {...},
    "inbound_call": {...},
    "state": "new" | "updated"
}

Without contact (when the inbound call has no contact_id):

{
    "inbound_call": {...},
    "state": "new" | "updated"
}
  • contact - Contains the contact details (only if the inbound call is associated with a contact).
  • inbound_call - Contains the inbound call details (call_time, state, calling_number, called_number, disposition, etc.).
  • state - new for a new inbound call, updated when the call state changes (e.g., from "ringing" to "answered").
Rate Limiting

Webhook mode supports configurable rate limiting to protect your web service:

  • --wr - Requests per second (default: 5)
  • --wb - Burst limit (default: 2 × wr)
Example

Transfer all future transactions and inbound calls in the campaign MY_CAMPAIGN to a Webservice with a rate limit of 10 requests per second:

./dbsync2 --a webhook --c MY_CAMPAIGN_ID --ct MY_CAMPAIGN_TOKEN --url 'https://example.com/api/transactions/' --wr 10 --wb 20
PgBouncer Compatibility

dbsync2 v1.18+ is fully compatible with PgBouncer transaction pooling mode (pool_mode=transaction) without any special configuration.

Technical Implementation:

  • Uses pgx/v5 driver instead of lib/pq
  • Configured with DefaultQueryExecMode = QueryExecModeSimpleProtocol to avoid server-side prepared statements
  • Automatically applied to all PostgreSQL connections - no user configuration required

Version History:

  • v1.18+: Uses pgx/v5 driver with simple protocol mode - fully compatible with PgBouncer transaction pooling
  • v1.17: Attempted fix using direct SQL execution, but lib/pq driver still created server-side prepared statements internally
  • v1.16 and earlier: Used explicit prepared statements, incompatible with transaction pooling

If you're using an older version and encounter errors like pq: prepared statement does not exist or bind message supplies ... parameters, please upgrade to v1.18+ or configure PgBouncer to use pool_mode=session for your database.

Sync Timing Options (db_sync)

In db_sync mode the client combines a per-minute live poll with a periodic catch-up that re-lists recently changed contacts. Connection data (e.g. the call duration of a connect) is sometimes attached to a contact with a delay; if it is attached after the contact has dropped out of the rolling catch-up window, it is only picked up once another event touches the contact. The following options make the relevant windows configurable. The defaults reproduce the previous behaviour, so omitting them changes nothing.

Option Short Default Description
--catchup_window --cw 24h How far back the periodic catch-up re-lists changed contacts.
--catchup_interval --ci 12h How often the periodic catch-up runs.
--live_lookback --ll 2m Look-back window of the per-minute live poll. Increase (e.g. 15m) so connection data attached a few minutes after the call is still picked up live.
--live_interval --li 1m How often the live poll runs.

All values use Go duration syntax (e.g. 90m, 24h, 168h).

Example

Run a continuous sync where delayed connect connection data is reconciled for up to seven days:

dbsync2 --a db_sync --c MY_CAMPAIGN_ID --ct MY_CAMPAIGN_TOKEN --catchup_window 168h --url 'postgres://my_user:my_password@localhost:5432/my_database?sslmode=disable'

A wider catch-up window re-lists and re-fetches more contacts per cycle, which increases load. Tune --catchup_window / --catchup_interval to balance completeness against load. The live event-dedup cache automatically follows --live_lookback.

Log and State Files
  • Startup and configuration errors are written directly to the console (stderr).
  • All log messages are written to /var/log/dbsync2/{MY_CAMPAIGN_ID}_{TIMESTAMP}.log.
  • If /var/log/dbsync2/ is not writable, the log falls back to $HOME/.dbsync2/log/{TIMESTAMP}.log.
  • Use --v to print the log to stdout instead of writing it to a file, and --l 5 for debug level output. This is the fastest way to see what a run is actually doing.
  • The resume timestamp is stored per campaign in /var/opt/dbsync2/{MY_CAMPAIGN_ID}.json, falling back to $HOME/.dbsync2/{MY_CAMPAIGN_ID}.json. Delete this file to make db_sync start over from the --s start date.
  • Connection URLs are written to the log with the password replaced by *** (as of v1.20.2). Older versions log the full URL including the password — take care when sharing log output.

Complete Examples per Database

Each example is self-contained: create the database and the user, import the history once with db_init, then keep it up to date with db_sync. Replace MY_CAMPAIGN_ID / MY_CAMPAIGN_TOKEN with the values from your campaign, and pick a real password.

The user dbsync2 connects with needs permission to create and alter tables in the target database, plus the usual SELECT / INSERT / UPDATE. It reads information_schema (respectively sys.tables) to detect existing columns.

PostgreSQL

1. Create database and user

sudo -u postgres psql <<'SQL'
CREATE USER dbsync WITH PASSWORD 'my_password';
CREATE DATABASE dialfire OWNER dbsync;
SQL

Making dbsync the owner is the simplest working setup. If you must use a database you do not own, grant the rights on the schema explicitly (required from PostgreSQL 15 on, where public is no longer writable by everyone):

GRANT CREATE, USAGE ON SCHEMA public TO dbsync;

2. Import the history once

./dbsync2 --a db_init \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'postgres://dbsync:my_password@localhost:5432/dialfire?sslmode=disable'

3. Sync continuously

./dbsync2 --a db_sync \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'postgres://dbsync:my_password@localhost:5432/dialfire?sslmode=disable'

Notes:

  • Use sslmode=require (or verify-full) instead of disable for a remote server.
  • Column names contain $ and mixed case, so they must be double-quoted in queries: SELECT "$id", "$phone" FROM contacts;
  • Works behind PgBouncer in pool_mode=transaction — see PgBouncer Compatibility.
  • To put the tables into a dedicated schema, add &options=-csearch_path%3Dmy_schema and create the schema beforehand.
MySQL / MariaDB

1. Create database and user

sudo mysql <<'SQL'
CREATE DATABASE dialfire CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'dbsync'@'%' IDENTIFIED BY 'my_password';
GRANT SELECT, INSERT, UPDATE, CREATE, ALTER, INDEX ON dialfire.* TO 'dbsync'@'%';
FLUSH PRIVILEGES;
SQL

2. Import the history once

./dbsync2 --a db_init \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'mysql://dbsync:my_password@localhost:3306/dialfire?tls=false'

3. Sync continuously

./dbsync2 --a db_sync \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'mysql://dbsync:my_password@localhost:3306/dialfire?tls=false'

Notes:

  • dbsync2 appends charset=utf8mb4 and collation=utf8mb4_general_ci to the connection automatically if you do not set them, so emojis and non-Latin scripts survive. Create the database with a utf8mb4 charset as shown, otherwise the tables inherit a narrower server default.

  • Connection parameters are Go driver parameters, not JDBC ones. TLS is controlled by tls=false / tls=true / tls=skip-verify — the JDBC spelling useSSL=false is not understood and is forwarded to the server as SET useSSL = false, which fails with Error 1193: Unknown system variable 'useSSL'. Other useful parameters: timeout, readTimeout, writeTimeout, maxAllowedPacket. The full list is in the go-sql-driver documentation.

  • mariadb:// is accepted as an alias for mysql:// (v1.20.2+).

  • Column names contain $, so they must be quoted with backticks in queries:

    SELECT `$id`, `$phone` FROM contacts;
    
Microsoft SQL Server

1. Create database and login

CREATE DATABASE dialfire;
GO
CREATE LOGIN dbsync WITH PASSWORD = 'My_Strong_Password1';
GO
USE dialfire;
CREATE USER dbsync FOR LOGIN dbsync;
ALTER ROLE db_owner ADD MEMBER dbsync;
GO

2. Import the history once

./dbsync2 --a db_init \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'sqlserver://dbsync:My_Strong_Password1@localhost:1433/dialfire'

3. Sync continuously

./dbsync2 --a db_sync \
  --c MY_CAMPAIGN_ID \
  --ct MY_CAMPAIGN_TOKEN \
  --url 'sqlserver://dbsync:My_Strong_Password1@localhost:1433/dialfire'

Notes:

  • For a named instance, put the instance before the database name: sqlserver://dbsync:My_Strong_Password1@localhost:1433/SQLEXPRESS/dialfire.
  • Numeric values are stored as nvarchar on SQL Server (see Data types), so arithmetic needs an explicit cast: SELECT SUM(CAST(edit_time_sec AS float)) FROM transactions;
  • Column names contain $, so they must be quoted with square brackets in queries: SELECT [$id], [$phone] FROM contacts;
  • mssql:// is accepted as an alias for sqlserver:// (v1.20.2+).
Filtered example

Transfer all transactions from 01 February 2018 in the campaign MY_CAMPAIGN to a local running instance of Microsoft SQL Server. Only updates that begin with the prefix 'fc_' or 'qc_' in campaign stages and have been performed by a user are to be transferred.

dbsync2 --a db_sync --fm hi_updates_only --fp 'fc_,qc_' --c MY_CAMPAIGN_ID --ct MY_CAMPAIGN_TOKEN --s 2018-02-01 --url 'sqlserver://my_user:my_password@localhost:1433/sql_server_instance/my_database'
Running as a service

db_sync is meant to run permanently. A minimal systemd unit (/etc/systemd/system/dbsync2.service):

[Unit]
Description=Dialfire dbsync2
After=network-online.target postgresql.service

[Service]
Type=simple
User=dbsync
Environment=DBSYNC_C=MY_CAMPAIGN_ID
Environment=DBSYNC_CT=MY_CAMPAIGN_TOKEN
Environment=DBSYNC_URL=postgres://dbsync:my_password@localhost:5432/dialfire?sslmode=disable
ExecStart=/usr/local/bin/dbsync2 --a db_sync
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
sudo install -d -o dbsync -g dbsync /var/log/dbsync2 /var/opt/dbsync2
sudo systemctl daemon-reload && sudo systemctl enable --now dbsync2
sudo journalctl -u dbsync2 -f

The install -d line matters: the service user needs to write the log directory and the state directory, otherwise both silently fall back to that user's home (see Log and State Files).

Passing the credentials as DBSYNC_* environment variables keeps the token out of the process list. See also Docker Compose Deployment.

Command Line Options

dbsync2 --help prints this list with the built-in defaults and is always authoritative for the version you are running. Every option is a flag — there are no positional arguments — and every flag can be replaced by an environment variable named DBSYNC_ + the flag name in upper case (--ctDBSYNC_CT).

Required
Option Value Description
--a webhook | db_init | db_update | db_sync Execution mode, see Execution Modes.
--c campaign id The campaign to export.
--ct token Sync Client token of that campaign, see Activation.
--url URL Target: database connection URL, or the webhook endpoint in mode webhook.
Selecting what is exported
Option Default Description
--s mode-dependent Start date, e.g. 2018-02-01 or 2018-02-01T14:30:00. The contact listing evaluates the date part only, so a time component does not narrow it down further. When omitted: db_init fetches the whole history, db_update starts one week ago, db_sync resumes from its saved state.
--fm (no filter) Transaction filter mode: updates_only for transactions of type update only, hi_updates_only for updates triggered by a human interaction only.
--fp (no filter) Only transactions from tasks with these prefixes, comma separated, e.g. 'fc_,qc_'.
--cf (all fields) Long form --contact_fields. Only create columns for these campaign fields, comma separated, * wildcard allowed, e.g. 'kd_*,email'. --cf none creates no campaign columns at all, leaving only the $ system columns. Matching is case-sensitive (v1.21.1+); a pattern that matches no field is reported as a warning.
--cfx (nothing excluded) Long form --contact_fields_exclude. Do not create columns for these campaign fields, same syntax as --cf, applied after it.
Database target
Option Default Description
--tp (none) Prefix for all table names, e.g. --tp acme_ creates acme_contacts, acme_transactions, … Lets several campaigns share one database.
--d 32 Maximum number of simultaneous database connections. Lower it if the database refuses connections, raise it if chanDatabaseUpdater keeps growing.
--w 32 Number of worker goroutines used for fetching and splitting.
Webhook target
Option Default Description
--wr 5 Rate limit in requests per second, shared between transactions and inbound calls.
--wb 2 × wr Burst limit.
Sync timing (mode db_sync only)
Option Short Default Description
--catchup_window --cw 24h How far back the periodic catch-up re-lists changed contacts.
--catchup_interval --ci 12h How often the periodic catch-up runs.
--live_lookback --ll 2m Look-back window of the live poll.
--live_interval --li 1m How often the live poll runs.

Go duration syntax (90m, 24h, 168h). Details and tuning advice in Sync Timing Options.

Logging and diagnostics
Option Default Description
--v off Print all log messages to stdout instead of writing a log file. Also raises the log level to debug.
--l 4 Log level: 0 CRITICAL, 1 ERROR, 2 WARNING, 3 NOTICE, 4 INFO, 5 DEBUG.
--p off Enable the pprof profiling server on port 8080 (http://localhost:8080/debug/pprof/).
--version Print version, commit and build date, then exit.
--help Print the usage text with examples, then exit.

Caveats and Operational Notes

Schema handling
  • The database must exist; dbsync2 creates only the tables in it. Tables and columns are created with IF NOT EXISTS semantics, so re-running db_init against a populated database is safe — rows are upserted, not duplicated.

  • Every non-deleted campaign field becomes a column in contacts. New campaign fields are picked up automatically: db_sync re-reads the field list once per hour and adds the missing columns.

  • Columns are only ever added, never changed or removed. If a campaign field is deleted, its column stays behind (with its data). If a field changes its type — e.g. from number to text — the existing column keeps the old SQL type, which can cause insert errors for values that no longer fit. Drop or convert such a column manually.

  • Only one index is created: contacts($id, $md5). There are no indexes on the join columns, so add your own before running reporting queries over large tables:

    CREATE INDEX idx_transactions_contact ON transactions ("contact_id");
    CREATE INDEX idx_connections_transaction ON connections ("transaction_id");
    CREATE INDEX idx_recordings_connection ON recordings ("connection_id");
    CREATE INDEX idx_inbound_calls_contact ON inbound_calls ("contact_id");
    
  • The contact_id / transaction_id / connection_id columns are logical references. No FOREIGN KEY constraints are created, and rows can arrive out of order, so a child row may briefly exist before its parent.

Campaigns with many fields

Every campaign field becomes its own column in contacts, and every DBMS limits how wide one row may be. On MySQL/MariaDB (InnoDB, 16 KB pages) the limit is 8126 bytes and the sync aborts with:

ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT or BLOB
may help. In current row format, BLOB prefix of 0 bytes is stored inline.

What counts is the number of variable-length columns, not their declared width: in the row-size check InnoDB reserves about 41 bytes for every varchar and every text column alike, because it may have to store the value off-page. Measured on MySQL 8.4 with the default DYNAMIC row format:

Table content Columns that still work
varchar(50) columns only 196
text columns only 196
numeric columns only > 600 (no limit reached)
dbsync2 contacts (22 $ columns + campaign fields) 174 campaign fields, 175 fails

So the 22 fixed $ columns consume 22 of roughly 196 available slots, leaving about 174 campaign fields of text type. Campaign fields of type number become numeric, which is fixed-length and does not count towards this budget — a campaign made up mostly of number fields can have far more fields.

Use --cf / --cfx (v1.21.0+) to decide which fields become columns:

# only the '$' system columns, no campaign fields at all
./dbsync2 --a db_sync --c MY_CAMPAIGN_ID --ct MY_CAMPAIGN_TOKEN --cf none \
  --url 'mysql://dbsync:my_password@localhost:3306/dialfire?tls=false'

# only the fields you actually report on (case-sensitive!)
--cf 'kd_*,email,street,zip'

# everything except a few large groups
--cfx 'notes_*,internal_*'

Both options only ever prevent columns from being created. Columns that already exist are never dropped automatically, so after narrowing the selection drop the leftovers yourself — dbsync2 will not recreate them:

ALTER TABLE contacts DROP COLUMN `notes_1`, DROP COLUMN `notes_2`;

Data for fields without a column is skipped on write, so no error is raised for the values that are no longer stored. Narrowing the selection is therefore enough to get a stalled sync running again — no change to the existing table is needed.

Dropping the leftover columns is still worth doing, and not only for tidiness: a column that is no longer part of the selection is never written again, so it keeps its last synced value for existing contacts while new contacts get NULL. The column then holds a mix of stale and empty values, which is easy to misinterpret in reports.

Other things worth knowing:

  • Patterns are case-sensitive (since v1.21.1). Dialfire field names are case-sensitive and mixed case is common — a campaign can contain Case, ID and agent_id side by side — so --cfx 'id' does not exclude ID. If a pattern matches no field of the campaign, dbsync2 logs a warning naming it, which is how a wrong capitalisation shows up.
  • Setting a field to hidden in the campaign does not exclude it. dbsync2 filters on deleted only and ignores the field state, so hidden fields still become columns.
  • Dropping columns without setting --cf/--cfx does not help either: the next schema refresh adds them back and hits the same limit. In db_sync the schema is refreshed every hour, so the failure repeats.
  • Check the row format with SHOW TABLE STATUS LIKE 'contacts'. DYNAMIC (the default since MySQL 5.7) is what makes the 196 columns above possible; with the older COMPACT format each text column reserves a 768 byte prefix inline and only 10 of them fit. ALTER TABLE contacts ROW_FORMAT=DYNAMIC fixes that.
  • MySQL column names are not case-sensitive, so two campaign fields whose names differ only in case (kunde and Kunde) cannot both become columns — the table creation fails with ERROR 1060: Duplicate column name. PostgreSQL keeps them apart because dbsync2 quotes identifiers. If you hit this on MySQL, exclude one of the two with --cfx 'Kunde'.
  • PostgreSQL allows 1600 columns per table and SQL Server 8060 bytes per row, so both are affected in the same way, just at a different threshold.
Data types
  • Timestamps are stored as strings (varchar(50), UTC, ISO-8601-like), not as native date/time types. Comparisons work lexicographically for equal-length values; for date arithmetic cast explicitly, e.g. WHERE fired::timestamp > now() - interval '1 day' on PostgreSQL.
  • On SQL Server, numeric values are stored as nvarchar(50), so SUM()/AVG() need a CAST(... AS float). On PostgreSQL and MySQL numbers use numeric.
  • Custom campaign fields are created as text and are not length-limited. The fixed $… system columns are varchar(50) / varchar(100), and values longer than the column are silently truncated.
  • On PostgreSQL, identifiers longer than 63 bytes are shortened and given a deterministic 8-character hash suffix, so a very long campaign field name will not appear verbatim as a column name. The mapping is stable across runs.
  • Nested values (objects/arrays) are stored as JSON text, and empty strings are written as NULL.
Operations
  • State: the resume timestamp lives in /var/opt/dbsync2/{CAMPAIGN_ID}.json (fallback $HOME/.dbsync2/{CAMPAIGN_ID}.json) and is written every minute and on shutdown. Delete it to make db_sync start over from --s.
  • Restarts are cheap. dbsync2 upserts by primary key and compares MD5 hashes, so re-processing the same period does not duplicate or corrupt rows. After longer downtime either just start db_sync (it resumes) or run db_update --s <date> first.
  • Shutdown: SIGINT/SIGTERM drain the workers and save the state before exiting. Give the process a few seconds; killing it with SIGKILL loses at most the last minute of progress, which the next run re-fetches.
  • A wrong campaign id or token exits immediately with a 403 on the API URL. That is a credential problem, not a network problem.
  • One process per campaign. Two processes syncing the same campaign into the same tables will fight over the same rows and the same state file. To collect several campaigns in one database, use one process each with distinct --tp prefixes.
  • Bottlenecks are visible in the log every 30 seconds as Channel lengths: chanDataSplitter=… chanContactFetcher=… chanDatabaseUpdater=…. A permanently growing chanDatabaseUpdater means the database is the limit (raise --d, add indexes, check disk); a growing chanDataSplitter means too few workers (raise --w).
  • Clock skew matters: start dates and the resume timestamp are UTC and come from the local clock. Keep the host on NTP.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
Package logging implements a logging infrastructure for Go.
Package logging implements a logging infrastructure for Go.
spf13/pflag
Package pflag is a drop-in replacement for Go's flag package, implementing POSIX/GNU-style --flags.
Package pflag is a drop-in replacement for Go's flag package, implementing POSIX/GNU-style --flags.
maps
Package maps provides reusable functions for manipulating nested map[string]interface{} maps are common unmarshal products from various serializers such as json, yaml etc.
Package maps provides reusable functions for manipulating nested map[string]interface{} maps are common unmarshal products from various serializers such as json, yaml etc.
providers/env
Package env implements a koanf.Provider that reads environment variables as conf maps.
Package env implements a koanf.Provider that reads environment variables as conf maps.
providers/posflag
Package posflag implements a koanf.Provider that reads commandline parameters as conf maps using spf13/pflag, a POSIX compliant alternative to Go's stdlib flag package.
Package posflag implements a koanf.Provider that reads commandline parameters as conf maps using spf13/pflag, a POSIX compliant alternative to Go's stdlib flag package.
Package mapstructure exposes functionality to convert an arbitrary map[string]interface{} into a native Go structure.
Package mapstructure exposes functionality to convert an arbitrary map[string]interface{} into a native Go structure.
Package pflag is a drop-in replacement for Go's flag package, implementing POSIX/GNU-style --flags.
Package pflag is a drop-in replacement for Go's flag package, implementing POSIX/GNU-style --flags.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL