This tutorial builds a small native C program that registers an IllyVoIP SIP account with liblinphone, reports its registration state and unregisters before exiting. It does not dial, answer a call or send a message. Use it to establish the account lifecycle before adding calling features to your application.
This is for developers using the Linphone SDK, not people configuring the ready-made Linphone app. The example was compiled against liblinphone 5.2.0 development headers and exercised against a local digest-authentication fixture. Check the API and packaging of the exact SDK release you ship. Upstream's current API documentation describes newer releases as well.

1. Prepare the SDK and a dedicated SIP line
Use a Linux development environment with a C11 compiler, liblinphone development headers, matching runtime libraries and the SDK's resource files. A missing grammar/resource file is a packaging problem, not an IllyVoIP authentication failure. Install a complete supported SDK package through your normal development process.
Review the SDK's license before embedding or distributing it. Liblinphone uses an open-source AGPL licensing model with commercial licensing options; suitability depends on your application and distribution. This example does not grant a license to the SDK.
- In IllyVoIP, open Services → SIP Lines and choose a password-authenticated test line that is not already serving a live reception device.
- Record its SIP user identity and authentication name, and obtain the password securely.
- Expand Technical endpoints and record the SIP hostname, destination port and transport.
- Keep the identity domain and registrar connection separate if your account supplies different values.
- Do not point this first registration test at a customer-facing incoming number.
The Portal login and API key do not authenticate a SIP account. A SIP identity URI represents the user, while the server URI selects the registrar connection. For TLS, use the supplied hostname rather than an IP address and keep certificate and hostname verification enabled.
2. Store the secret outside source code and arguments
Work in a private directory. The following prompt creates a new owner-only file without putting the password in shell history or a command-line argument. It refuses to overwrite an existing file:
python3 - <<'PYSECRET'
import getpass, os
os.umask(0o077)
secret = getpass.getpass("SIP password: ")
if not secret or "\n" in secret or "\r" in secret or len(secret.encode()) > 510:
raise SystemExit("Use one nonempty password of at most 510 bytes.")
with open("sip-secret.txt", "x", encoding="utf-8") as f:
f.write(secret + "\n")
PYSECRETKeep this file out of version control, backups shared with others and build artifacts. On a real application, use an appropriate platform secret store. The example reads a single newline-terminated secret from standard input, then clears its temporary buffer after the SDK has received it. The SDK necessarily retains authentication material in memory while the account is active.
3. Save the registration example
Save the following as register.c. It uses an in-memory account configuration, a separate private SDK data directory, random local signaling ports, registration callbacks and bounded shutdown. It does not log the password or echo the remote registration diagnostic text.
#define _POSIX_C_SOURCE 200809L
#include <linphone/linphonecore.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t stopping;
static void on_signal(int number) { (void)number; stopping = 1; }
static double seconds(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return t.tv_sec + t.tv_nsec / 1e9;
}
static void tick(LinphoneCore *core) {
struct timespec delay = {0, 20000000};
linphone_core_iterate(core);
nanosleep(&delay, NULL);
}
static void state_changed(LinphoneCore *core, LinphoneAccount *account,
LinphoneRegistrationState state, const char *message) {
(void)core; (void)account; (void)message;
/* Avoid logging identities, credentials or remote diagnostic text. */
printf("Registration: %s\n", linphone_registration_state_to_string(state));
fflush(stdout);
}
static void erase(char *buffer, size_t size) {
volatile char *p = buffer;
while (size--) *p++ = 0;
}
int main(int argc, char **argv) {
char secret[512] = {0};
if (argc != 5 || isatty(STDIN_FILENO)) {
fputs("Usage: register IDENTITY_URI SERVER_URI AUTH_NAME PRIVATE_DATA_DIR < private-secret-file\n", stderr);
return 2;
}
if (!fgets(secret, sizeof(secret), stdin) || !strchr(secret, '\n')) {
erase(secret, sizeof(secret));
fputs("Secret must be one newline-terminated line, at most 510 bytes.\n", stderr);
return 2;
}
secret[strcspn(secret, "\r\n")] = 0;
if (!secret[0] || fgetc(stdin) != EOF) {
erase(secret, sizeof(secret));
fputs("Use exactly one nonempty secret line.\n", stderr); return 2;
}
signal(SIGINT, on_signal); signal(SIGTERM, on_signal);
linphone_logging_service_set_log_level_mask(linphone_logging_service_get(), 0);
LinphoneAddress *identity = linphone_address_new(argv[1]);
LinphoneAddress *server = linphone_address_new(argv[2]);
if (!identity || !server || !linphone_address_get_username(identity) ||
!linphone_address_get_domain(identity) || !linphone_address_get_domain(server)) {
fputs("Invalid SIP identity or server URI.\n", stderr);
if (identity) linphone_address_unref(identity);
if (server) linphone_address_unref(server);
erase(secret, sizeof(secret)); return 2;
}
linphone_factory_set_data_dir(linphone_factory_get(), argv[4]);
LinphoneCore *core = linphone_factory_create_core_3(linphone_factory_get(), NULL, NULL, NULL);
LinphoneCoreCbs *callbacks = linphone_factory_create_core_cbs(linphone_factory_get());
linphone_core_cbs_set_account_registration_state_changed(callbacks, state_changed);
linphone_core_add_callbacks(core, callbacks);
linphone_core_cbs_unref(callbacks);
LinphoneTransports *ports = linphone_factory_create_transports(linphone_factory_get());
linphone_transports_set_udp_port(ports, LC_SIP_TRANSPORT_RANDOM);
linphone_transports_set_tcp_port(ports, LC_SIP_TRANSPORT_RANDOM);
linphone_transports_set_tls_port(ports, LC_SIP_TRANSPORT_RANDOM);
linphone_core_set_transports(core, ports);
linphone_transports_unref(ports);
linphone_core_verify_server_certificates(core, TRUE);
linphone_core_verify_server_cn(core, TRUE);
/* No config path: do not persist the account or secret to disk. */
if (linphone_core_start(core) != 0) {
fputs("Core start failed.\n", stderr);
linphone_core_unref(core); linphone_address_unref(identity);
linphone_address_unref(server); erase(secret, sizeof(secret)); return 1;
}
LinphoneAuthInfo *auth = linphone_auth_info_new(
linphone_address_get_username(identity), argv[3], secret,
NULL, NULL, linphone_address_get_domain(identity));
linphone_core_add_auth_info(core, auth);
linphone_auth_info_unref(auth); erase(secret, sizeof(secret));
LinphoneAccountParams *params = linphone_core_create_account_params(core);
int invalid = linphone_account_params_set_identity_address(params, identity) != 0 ||
linphone_account_params_set_server_address(params, server) != 0;
linphone_account_params_enable_register(params, TRUE);
LinphoneAccount *account = invalid ? NULL : linphone_core_create_account(core, params);
linphone_account_params_unref(params);
linphone_address_unref(identity); linphone_address_unref(server);
int result = 1;
if (account && linphone_core_add_account(core, account) == 0) {
double deadline = seconds() + 30;
while (!stopping && seconds() < deadline) {
tick(core);
LinphoneRegistrationState state = linphone_account_get_state(account);
if (state == LinphoneRegistrationOk) { result = 0; break; }
if (state == LinphoneRegistrationFailed) break;
}
if (result) fputs("Registration failed, cancelled or timed out.\n", stderr);
LinphoneAccountParams *off = linphone_account_params_clone(linphone_account_get_params(account));
linphone_account_params_enable_register(off, FALSE);
linphone_account_set_params(account, off);
linphone_account_params_unref(off);
deadline = seconds() + 5;
while (seconds() < deadline && linphone_account_get_state(account) != LinphoneRegistrationCleared)
tick(core);
if (linphone_account_get_state(account) != LinphoneRegistrationCleared) {
fputs("Unregister not confirmed; server registration may remain until expiry.\n", stderr);
if (!result) result = 3;
}
linphone_core_remove_account(core, account);
} else fputs("Account configuration rejected.\n", stderr);
if (account) linphone_account_unref(account);
linphone_core_clear_all_auth_info(core);
linphone_core_stop(core); linphone_core_unref(core);
linphone_factory_clean();
return result;
}
The identity's username and the authentication name are passed separately. The core owns the active account and authentication objects while running; local references are released. Account changes are processed by repeatedly calling linphone_core_iterate on the same thread. A real graphical application should integrate that work with its supported event loop rather than block the UI.
4. Build and run deliberately
With development headers and libraries installed in the compiler's normal search paths:
cc -std=c11 -Wall -Wextra register.c -llinphone -o registerFor an SDK installed in a custom prefix, use that release's documented compiler, linker and resource paths. Keep headers and libraries from the same release. Do not fix a missing symbol by mixing unrelated SDK builds.
Replace every uppercase placeholder below with the values for your line. The server URI's destination port and transport must match the endpoint; the example shows the syntax for TLS, not a claim that every supplied connection uses TLS:
umask 077
mkdir -m 700 illy-sdk-data
./register \
'sip:YOUR_SIP_USER@YOUR_SIP_DOMAIN' \
'sip:YOUR_SIP_HOST:YOUR_PORT;transport=tls' \
'YOUR_AUTHENTICATION_NAME' \
"$PWD/illy-sdk-data" < sip-secret.txtFor a supplied UDP or TCP endpoint, use that transport and its matching port. The private data directory is for SDK internal files; no persistent SIP configuration path is supplied. Keep that directory private as well. Do not run a registration test under a shared desktop application's data directory.
5. Interpret the result
A normal successful sequence includes LinphoneRegistrationProgress, LinphoneRegistrationOk and then LinphoneRegistrationCleared. The program intentionally unregisters immediately after proving registration. It is not a background softphone that stays online for incoming calls.
| Exit status | Meaning |
|---|---|
| 0 | Registration succeeded and unregister was confirmed. |
| 1 | Startup/account setup failed, registration failed, was cancelled or timed out. |
| 2 | Arguments, SIP URI or secret-file input were invalid. |
| 3 | Registration succeeded, but unregister was not confirmed before the shutdown deadline. |
An initial SIP authentication challenge is normal when followed by a successful authenticated registration. A final rejection or a timeout is different. If unregister is not confirmed, the server may retain the contact until its expiry; the program does not falsely report cleanup as successful.
6. Diagnose before adding calls
- Core startup fails: check complete SDK resources, writable private data-directory permissions and matching runtime libraries.
- Authentication fails: compare identity username, authentication name, domain and the exact password from the same line.
- TLS fails: check the endpoint hostname, destination port, clock and CA trust. Keep certificate verification enabled.
- Registration times out: check DNS, endpoint transport and network policy before repeatedly changing the secret.
- Other phones stop ringing: stop sharing a production identity for tests and investigate contact replacement or registration policy.
Keep diagnostic logs private: SDK or transport logs can contain SIP identities, addresses and call metadata even when this callback prints only state names. Do not enable verbose logging and post its raw output publicly.
What this example proves—and what comes next
Local checks covered an authenticated registration using distinct identity/authentication names, clean unregister, a rejected registration, a lost unregister response and invalid inputs. They did not connect to IllyVoIP, validate a live TLS handshake or test audio.
Before implementing calls, separately handle audio devices and permissions, incoming-call policy, codec negotiation and the IllyVoIP line's RTP/SRTP setting. TLS registration is not an audio-encryption test. Use deliberate, authorized destinations for any chargeable call and verify number routing separately. The Linphone SDK does not replace the browser-specific IllyVoIP Webphone SDK.
References: Linphone developer documentation and liblinphone registration API tutorial. Reviewed October 11, 2026. The code here is an independent bounded example rather than a verbatim copy of the upstream tutorial.