Docs Add Homer to your voice stack

Add Homer to your voice stack

Run heplify-server, homer-app and PostgreSQL, have OpenSIPS or Kamailio send every call to Homer over HEP, and find the call. No sipnab involved.

On this page

Homer keeps a searchable history of your SIP traffic. Your SIP servers send it a copy of every message they handle, wrapped in HEP (the Homer Encapsulation Protocol), and Homer stores each one and shows you any past call as a ladder of messages. It answers “what happened to that call yesterday” without anyone having run a capture at the time.

This guide stands up Homer beside an OpenSIPS proxy and has OpenSIPS send it every call. With Kamailio instead, one section replaces the OpenSIPS steps. This guide does not use sipnab. When you have this working, Connect sipnab to Homer adds sipnab as a second receiver and as another source.

The parts, and what each one does:

PartRole
heplify-serverReceives HEP on UDP port 9060 and writes each message into PostgreSQL.
PostgreSQLStores the messages, one table per message type and day.
homer-appThe web interface and its API, on port 9080. It reads what heplify-server stored.
OpenSIPSYour SIP proxy. Its tracer module copies every message of a call to heplify-server over HEP.

Everything below runs on one machine, which keeps the example short. The last section says what changes when they are apart.

Tested on

Every command on this page ran as written, in order, on x86_64 virtual machines with 2 cores and 3 or 4 GB of memory:

  • With OpenSIPS built from source: on a clean Debian 13 (kernel 6.12.63) and Ubuntu 24.04.5 (kernel 6.8.0) on 2026-09-26, with an earlier step 4 that named the source build’s paths; and on Debian 13 on 2026-09-27, with OpenSIPS already built by the rtpengine guide.
  • With the OpenSIPS 4.0 packages: on Ubuntu 24.04.5, on 2026-09-27.
  • With Kamailio: on Debian 13, and beside OpenSIPS on Ubuntu 24.04.5, on 2026-09-27.

The commands pin the components to the versions below.

SoftwareVersion or commit
Docker Engine / Composefrom the Docker apt repository
heplify-serverghcr.io/sipcapture/heplify-server:1.60.9
homer-appghcr.io/sipcapture/homer-app:1.5.21
PostgreSQLpostgres:17.11-alpine
OpenSIPS, built heref46ef9337b, master, 4.1.0-dev
OpenSIPS, from packages4.0.2, installed as the OpenSIPS guide installs it
Kamailio6.1.4, installed as the Kamailio guide installs it
SIPp (for the test call)the distribution’s sip-tester

The examples use 192.0.2.10 as the machine’s address. Replace it with yours everywhere it appears.

Before you start: your SIP server

Find your case, and follow the steps it names:

  • No SIP server yet. Follow every step. Step 3 builds OpenSIPS for you. If you would rather run the OpenSIPS 4.0 packages, install them with step 1 of the OpenSIPS guide and skip step 3 here.
  • OpenSIPS already runs, from the packages or built from source. Skip step 3. The proto_hep, tracer and dialog modules come with the opensips package and with the default build, so there is nothing to add. Step 4 finds your configuration file and replaces it. On a machine whose script you want to keep, add the lines step 4 marks to your own script instead.
  • Kamailio already runs, or you want Kamailio. Follow steps 1 and 2, then With Kamailio in place of steps 3 and 4, then steps 5 to 7.
  • Both OpenSIPS and Kamailio on one machine. Set up OpenSIPS as above, then see With Kamailio, which covers both.

1. Install Docker

Homer runs as containers. Install Docker Engine and the Compose plugin from the Docker repository:

# Run all of these, in order.
sudo apt-get update
sudo apt-get install -y ca-certificates curl git
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/$ID/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $VERSION_CODENAME stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"

Log out and back in so that your account’s new docker group takes effect, then check:

docker compose version

2. Start Homer

The three services below are the ones in the sipcapture project’s own homer7-docker recipe, without the Prometheus, Grafana and Loki it also bundles. Choose a database password and put it in .env, where Compose reads it:

# Run all of these, in order.
sudo mkdir -p /opt/homer && sudo chown "$USER": /opt/homer
cd /opt/homer
echo "DB_PASS=$(openssl rand -hex 16)" > .env
chmod 600 .env
cat > init-user-db.sh <<'EOF'
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
	CREATE DATABASE homer_config;
EOSQL
EOF
cat > docker-compose.yml <<'EOF'
services:
  db:
    image: postgres:17.11-alpine
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: ${DB_PASS}
    volumes:
      - ./init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh:ro
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "psql -h localhost -U root -c '\\l'"]
      interval: 2s
      timeout: 3s
      retries: 30
    restart: unless-stopped

  heplify-server:
    image: ghcr.io/sipcapture/heplify-server:1.60.9
    command: ["./heplify-server"]
    ports:
      - "9060:9060/udp"
    environment:
      HEPLIFYSERVER_HEPADDR: "0.0.0.0:9060"
      HEPLIFYSERVER_DBSHEMA: homer7
      HEPLIFYSERVER_DBDRIVER: postgres
      HEPLIFYSERVER_DBADDR: "db:5432"
      HEPLIFYSERVER_DBUSER: root
      HEPLIFYSERVER_DBPASS: ${DB_PASS}
      HEPLIFYSERVER_DBDATATABLE: homer_data
      HEPLIFYSERVER_DBCONFTABLE: homer_config
      HEPLIFYSERVER_DBROTATE: "true"
      HEPLIFYSERVER_DBDROPDAYS: "7"
      HEPLIFYSERVER_LOGLVL: info
      HEPLIFYSERVER_LOGSTD: "true"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  homer-app:
    image: ghcr.io/sipcapture/homer-app:1.5.21
    environment:
      DB_HOST: db
      DB_USER: root
      DB_PASS: ${DB_PASS}
    ports:
      - "9080:80"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

volumes:
  db-data:
EOF
docker compose up -d

HEPLIFYSERVER_DBDROPDAYS is how long Homer keeps messages: heplify-server drops each day’s tables after 7 days here.

The first start creates the tables, which took about 30 seconds on the test machines. heplify-server holds back what arrives until then, so a search before it has finished finds nothing. Wait until it logs that it has, and the web interface answers:

# Run all of these, in order.
cd /opt/homer
until docker compose logs heplify-server | grep -q 'end creating tables'; do sleep 2; done
until curl -fs -o /dev/null localhost:9080; do sleep 2; done; echo ready

Open http://192.0.2.10:9080 and log in as admin, password sipcapture. Change that password under the user settings before anything else can reach the port.

3. Build OpenSIPS

Skip this step if OpenSIPS is already installed, from the packages or from source.

OpenSIPS master builds with compiler optimizations turned off, which is right for OpenSIPS’s own developers and wrong for a proxy carrying calls. Turn them back on before you build. The proto_hep and tracer modules are part of the default build:

# Run all of these, in order.
sudo apt-get install -y --no-install-recommends build-essential bison flex uuid-dev pkg-config libncurses-dev libssl-dev
sudo mkdir -p /usr/local/src/voice && sudo chown "$USER": /usr/local/src/voice
cd /usr/local/src/voice
git clone https://github.com/OpenSIPS/opensips.git
cd opensips
git checkout f46ef9337b
make Makefile.conf
sed -i 's/^DEFS+= -DCC_O0/#DEFS+= -DCC_O0/' Makefile.conf
make -j2 all
sudo make install
/usr/local/sbin/opensips -V | head -2

Leave DBG_MALLOC as it is. With both it and CC_O0 switched off, this commit of master does not compile: net/tcp_conn_defs.h calls get_ticks() without including the header that declares it, and only DBG_MALLOC’s headers happen to supply it.

4. Configure OpenSIPS to send every call to Homer

This configuration is a minimal proxy with tracing added. Your own script does much more. The tracing part is the block marked below, and the loadmodule lines it needs.

Write the configuration

The packages and the source build keep their configuration and their modules in different places. The first two lines find them: C is your configuration file, and M the directory your install loads modules from, which the script’s mpath names.

# Run all of these, in order.
for f in /etc/opensips/opensips.cfg /usr/local/etc/opensips/opensips.cfg; do sudo test -f "$f" && C=$f && break; done
for d in /usr/lib/*/opensips/modules /usr/local/lib64/opensips/modules; do [ -f "$d/tm.so" ] && M=$d && break; done
echo "configuration: $C   modules: $M"
sudo tee "$C" >/dev/null <<'EOF'
# OpenSIPS as a SIP proxy that sends every call to Homer over HEP.
log_level=3
stderror_enabled=no
syslog_enabled=yes
syslog_facility=LOG_LOCAL0
udp_workers=2
open_files_limit=4096

socket=udp:192.0.2.10:5060   # the address your phones and carriers reach
socket=hep_udp:127.0.0.1:6061   # HEP to Homer leaves through this socket

mpath="MODULES/"

loadmodule "proto_udp.so"   # built into the core, but still loaded by name
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "sipmsgops.so"
loadmodule "dialog.so"

loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo")

# Tracing: where HEP goes, and a name for it that trace() uses.
loadmodule "proto_hep.so"
modparam("proto_hep", "hep_id", "[homer] 127.0.0.1:9060; transport=udp; version=3")
modparam("proto_hep", "hep_capture_id", 101)
loadmodule "tracer.so"
modparam("tracer", "trace_id", "[tid]uri=hep:homer")

route {
	if (!mf_process_maxfwd_header(10)) {
		send_reply(483, "Too Many Hops");
		exit;
	}

	if (has_totag()) {
		if (is_method("ACK") && t_check_trans()) {
			t_relay();
			exit;
		}
		if (!loose_route()) {
			send_reply(404, "Not here");
			exit;
		}
		t_relay();
		exit;
	}

	if (is_method("CANCEL")) {
		if (t_check_trans())
			t_relay();
		exit;
	}
	t_check_trans();

	if (!is_method("INVITE")) {
		send_reply(405, "Method Not Allowed");
		exit;
	}

	record_route();

	# --- tracing starts here ---
	create_dialog();
	trace("tid", "d");
	# --- tracing ends here ---

	# Where the call goes. Here, a test callee on this machine; in your
	# stack, lookup("location"), dispatcher or a carrier.
	$du = "sip:127.0.0.1:5070";
	t_relay();
}
EOF
sudo sed -i "s|^mpath=\"MODULES/\"|mpath=\"$M/\"|" "$C"
sudo grep '^mpath=' "$C"

What the tracing lines do:

  • socket=hep_udp:... gives OpenSIPS a socket to send HEP from. Without it, OpenSIPS refuses to start: No binding found for protocol proto_hep.
  • hep_id names a HEP destination, homer: heplify-server on this machine, HEP version 3 over UDP. Without transport=udp, OpenSIPS sends version 3 over TCP, which this heplify-server does not listen on.
  • hep_capture_id is the number Homer shows as the source of these messages. Give each SIP server its own.
  • trace_id defines tid as “send to homer”.
  • create_dialog() makes OpenSIPS track the call, and trace("tid", "d") sends every message of that dialog, both legs, until it ends.

If you built OpenSIPS in step 3: give it a user and a unit

The packages come with an opensips user and a systemd unit. If you installed them, or if your source build already runs as a service, skip to Check the configuration and start OpenSIPS. A fresh build has neither. Run OpenSIPS as its own user, under systemd:

# Run all of these, in order.
sudo useradd --system --home-dir /run/opensips --shell /usr/sbin/nologin opensips
sudo chown root:opensips /usr/local/etc/opensips
sudo chmod 750 /usr/local/etc/opensips
sudo tee /etc/systemd/system/opensips.service >/dev/null <<'EOF'
[Unit]
Description=OpenSIPS SIP server
After=network.target docker.service

[Service]
Type=forking
User=opensips
Group=opensips
RuntimeDirectory=opensips
RuntimeDirectoryMode=775
PIDFile=/run/opensips/opensips.pid
ExecStart=/usr/local/sbin/opensips -P /run/opensips/opensips.pid -f /usr/local/etc/opensips/opensips.cfg -m 64 -M 8
Restart=always
TimeoutStopSec=30s
LimitNOFILE=262144

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload

Check the configuration and start OpenSIPS

# Run all of these, in order.
for f in /etc/opensips/opensips.cfg /usr/local/etc/opensips/opensips.cfg; do sudo test -f "$f" && C=$f && break; done
sudo chown root:opensips "$C"
sudo chmod 640 "$C"
sudo opensips -C -f "$C"
sudo systemctl enable opensips
sudo systemctl restart opensips
systemctl is-active opensips

opensips -C checks the configuration and prints config file ok before you start anything.

5. Place a test call

SIPp plays both ends: a callee on this machine, and a caller that dials through OpenSIPS. SIPp’s built-in caller ignores the Record-Route header OpenSIPS adds, so its BYE would miss the proxy and draw 404 Not here. The two sed lines make it honor the route set, the way a real phone does:

# Run all of these, in order.
sudo apt-get install -y sip-tester
mkdir -p ~/sipp && cd ~/sipp
sipp -sd uac > uac_rr.xml
sed -i 's|<recv response="200" rtd="true">|<recv response="200" rtd="true" rrs="true">|' uac_rr.xml
sed -i -E 's#^( *)(ACK|BYE) sip:\[service\]@\[remote_ip\]:\[remote_port\] SIP/2.0#\1\2 [next_url] SIP/2.0\n\1[routes]#' uac_rr.xml
sipp -sn uas -i 127.0.0.1 -p 5070 -m 1 -bg
sipp -sf uac_rr.xml 192.0.2.10:5060 -i 192.0.2.10 -p 5080 -s echo -m 1 -d 2000 -timeout 30s \
  -trace_msg -message_file uac.msg
grep -m1 -i '^Call-ID:' uac.msg

At the end SIPp’s statistics screen shows Successful call at 1. The last line prints the call’s Call-ID.

6. Find the call in Homer

The web interface at http://192.0.2.10:9080 searches what heplify-server stored. To check from the command line, query the database heplify-server writes. heplify-server writes in batches, at least every 4 seconds, so the first line waits for the last batch. Each row is one message, and OpenSIPS sent most of them twice, as it arrived and as it left:

# Run all of these, in order.
sleep 5
cd ~/sipp
CALL=$(grep -m1 -i '^Call-ID:' uac.msg | awk '{print $2}' | tr -d '\r')
cd /opt/homer
docker compose exec -T db psql -U root -d homer_data -c \
  "select create_date, data_header->>'method' as method
     from hep_proto_1_call where data_header->>'callid' = '$CALL' order by create_date"

7. Operate it

Check health and read the logs.

# Run all of these, in order.
cd /opt/homer && docker compose ps
docker compose logs --tail 20 heplify-server
systemctl is-active opensips

heplify-server logs a line of statistics every five minutes, counting the HEP packets it received and any it filtered or failed to store.

Keep more or fewer days. Change HEPLIFYSERVER_DBDROPDAYS in /opt/homer/docker-compose.yml and recreate heplify-server:

# Run all of these, in order.
cd /opt/homer
sed -i 's/HEPLIFYSERVER_DBDROPDAYS: "7"/HEPLIFYSERVER_DBDROPDAYS: "14"/' docker-compose.yml
docker compose up -d heplify-server

Restart. A restart of heplify-server loses the HEP packets sent while it is down: HEP over UDP is not retried.

# Run all of these, in order.
cd /opt/homer && docker compose restart
sudo systemctl restart opensips

Uninstall. docker compose down -v removes the containers and the database volume, which is every stored message:

# Run all of these, in order.
cd /opt/homer && docker compose down -v
sudo systemctl disable --now opensips

With Kamailio

Kamailio sends HEP with its own siptrace module, which comes with the kamailio package. Set Kamailio up as the Kamailio guide does, through its step 2, then follow steps 1 and 2 here for Homer. In place of steps 3 and 4, add tracing to Kamailio’s configuration. The lines marked tracing are the ones to add to your own script:

# Run all of these, in order.
sudo tee /etc/kamailio/kamailio.cfg >/dev/null <<'EOF'
#!KAMAILIO
# Kamailio as a SIP proxy that sends every call to Homer over HEP.
debug=2
log_stderror=no
log_facility=LOG_LOCAL0
children=2

listen=udp:192.0.2.10:5060   # the address your phones and carriers reach

loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "siputils.so"
loadmodule "textops.so"
loadmodule "pv.so"
loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "ctl.so"

# tracing: HEP version 3 to heplify-server, for every message
loadmodule "siptrace.so"
modparam("siptrace", "duplicate_uri", "sip:127.0.0.1:9060")
modparam("siptrace", "hep_mode_on", 1)
modparam("siptrace", "hep_version", 3)
modparam("siptrace", "hep_capture_id", 101)
modparam("siptrace", "trace_to_database", 0)
modparam("siptrace", "trace_on", 1)
modparam("siptrace", "trace_mode", 1)

request_route {
	if (!mf_process_maxfwd_header("10")) {
		sl_send_reply("483", "Too Many Hops");
		exit;
	}

	if (has_totag()) {
		if (loose_route()) {
			t_relay();
			exit;
		}
		if (is_method("ACK") && t_check_trans()) {
			t_relay();
		}
		exit;
	}

	if (is_method("CANCEL")) {
		if (t_check_trans()) {
			t_relay();
		}
		exit;
	}
	t_check_trans();

	if (!is_method("INVITE")) {
		sl_send_reply("405", "Method Not Allowed");
		exit;
	}

	record_route();

	# Where the call goes. Here, a test callee on this machine; in your
	# stack, lookup("location"), dispatcher or a carrier.
	$du = "sip:127.0.0.1:5070";
	t_relay();
}
EOF
sudo kamailio -c -f /etc/kamailio/kamailio.cfg
sudo systemctl enable kamailio
sudo systemctl restart kamailio
systemctl is-active kamailio

What the tracing lines do:

  • duplicate_uri is where the copies go: heplify-server on this machine.
  • hep_mode_on and hep_version wrap each copy in HEP version 3, over UDP.
  • hep_capture_id is the number Homer shows as the source of these messages. Give each SIP server its own.
  • trace_to_database 0 sends the copies to Homer only, not to a database of Kamailio’s own.
  • trace_on 1 and trace_mode 1 copy every message Kamailio receives and sends, with no call to sip_trace() in the script.

Steps 5 to 7 then work unchanged, with kamailio in place of opensips in the systemctl commands. Kamailio, too, sends most messages twice, as each arrived and as it left: the test call’s query in step 6 returns 13 rows.

With OpenSIPS on the same machine, Kamailio listens on 5062, as OpenSIPS and Kamailio on one machine sets it up: change 5060 to 5062 in the listen line above, and give it its own hep_capture_id, such as 102, so that Homer shows which proxy saw each message. Both send to the same heplify-server.

Put the parts on different machines

  • Homer on its own machine. In hep_id, replace 127.0.0.1 with that machine’s address, and bind the hep_udp socket to an address that can reach it, such as socket=hep_udp:192.0.2.10:6061. Open UDP 9060 on it to your SIP servers only, and port 9080 to the people who use the interface.
  • Several SIP servers. Give each one the same hep_id destination and its own hep_capture_id, so that Homer shows which server saw each message.

When something does not work

  • OpenSIPS does not start: No binding found for protocol proto_hep. The configuration loads proto_hep but has no socket=hep_udp:... line.
  • The search finds nothing. Check that heplify-server received anything: docker compose logs heplify-server. If it logged no packets, check the hep_id address and transport=udp.
  • hep_id without transport=udp. OpenSIPS sends HEP version 3 over TCP by default, and this heplify-server listens on UDP only.
  • The test call’s BYE gets 404 Not here. The caller ignored the route set. Use the edited uac_rr.xml, not SIPp’s built-in uac.