CLI tool
emiago/sipgo avatar
emiago/sipgo

sipgo: a Go SIP stack for servers, registrars and stateful proxies

SIP library for writing fast SIP services in GO

1,078 stars234 forksGoBSD-2-Clause

At a glance

What is it?
sipgo is a Go library for writing SIP services, with a parsing focused stack covering RFC 3261, RFC 3263, RFC 3581 and RFC 6026 over UDP, TCP, TLS, WS and WSS. It suits a Go team building a registrar, a proxy or a softphone, and it leaves media handling to a separate framework built on top of it.
Who is it for?
sipgo suits a Go team that needs a SIP server, registrar, proxy or client without writing a parser, and that is comfortable working at the transaction level rather than through a higher level framework.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A stack separated from the higher level frameworks

sipgo is a library for writing fast SIP services in Go, and the repository layout shows the layering. The core sits in the sip/ directory and holds the stack, with the RFC 3261, RFC 3581 and RFC 6026 behaviour, and the top level files are the handles that most applications use: ua.go for the user agent, server.go, client.go, and a family of dialog files that manage session state, including separate client and server dialog implementations. The top level also carries a fakes/ directory, a siptest/ directory with a test helper, testdata/, and the example/ tree. The important structural fact is what is not here. There is no media stack and no RTP handling in this repository. That lives in diago, a separate project described as a full VOIP library and framework with a media stack and listed as under active development, and the documentation says that call and media examples are in diago/examples rather than here. A CLI softphone, Gophone, is listed as a tool developed on top. So the shape is a deliberately small core library with the heavier frameworks built above it, which is what makes it usable as a dependency rather than an application.

Fetching the library and the transport set it supports

Adding sipgo to a Go module is a single command.

bash
go get github.com/emiago/sipgo

The module path is github.com/emiago/sipgo, and the go.mod file declares Go 1.23.0 with a toolchain line pointing at go1.24.2. The dependency list is short, which is a good sign for a protocol library. It pulls in gobwas/ws for WebSocket support, google/uuid for identifiers, icholy/digest for HTTP digest authentication, stretchr/testify for the tests, and golang.org/x/sync. The transport list is the one that matters for a deployment decision, and the documentation marks all five as supported: UDP, TCP, TLS, WS and WSS. That is a full set for a SIP server in practice, since a modern deployment needs TLS for the signalling between trusted hops and WebSocket or secure WebSocket for browser clients. The RFC coverage listed is RFC 3261 for SIP core, RFC 3263 for locating SIP servers through DNS, RFC 3581 for the symmetric response routing extension, and RFC 6026 for correct handling of 2xx responses to INVITE. Documentation is on the Go doc site for the module, and there is a separate README under sip/ for the stack itself.

User agent, server handle, client handle

The API is built around three handles, and the example in the README is short enough to read as a whole. You build a user agent, then a server handle and a client handle on top of it, then register the handlers you care about and start listeners.

go
ua, _ := sipgo.NewUA() // Build user agent
srv, _ := sipgo.NewServer(ua) // Creating server handle for ua
client, _ := sipgo.NewClient(ua) // Creating client handle for ua
srv.OnInvite(inviteHandler)
srv.OnAck(ackHandler)
srv.OnBye(byeHandler)

ctx, _ := signal.NotifyContext(ctx, os.Interrupt)
go srv.ListenAndServe(ctx, "udp", "127.0.0.1:5060")
go srv.ListenAndServe(ctx, "tcp", "127.0.0.1:5061")
go srv.ListenAndServe(ctx, "ws", "127.0.0.1:5080")
<-ctx.Done()

The server handle creates listeners and reacts to incoming requests, and the client handle creates transaction requests, and the two can coexist on the same user agent. The server handler is registered per method, so an INVITE handler, an ACK handler and a BYE handler are separate registrations, with the commented line for a registrar showing that OnRegister is the equivalent for that method. A detail that catches people is that the server can also be used by a client first process, in which case the client is told which host and port to use and that connection is reused until the user agent is closed, and any request that does arrive can still be handled through the server handle. The example uses signal.NotifyContext with os.Interrupt to shut the listeners down cleanly, which is the pattern to copy when you embed this in a service.

Transactions are where the control lives

The server transaction is passed to your handler, and that is the mechanism that gives you control over a call. A handler sends a provisional response, then a final one, and then waits for the transaction to terminate.

go
srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
    // Send provisional
    res := sip.NewResponseFromRequest(req, 100, "Trying", nil)
    tx.Respond(res)
    
    // Send OK. TODO: some body like SDP
    res := sip.NewResponseFromRequest(req, 200, "OK", body)
    tx.Respond(res)

    // Wait transaction termination.
    select {
    case m := <-tx.Acks(): // Handle ACK for response . ACKs on 2xx are send as different request
    case <-tx.Done():
        // Signal transaction is done. 
        // Check any errors with tx.Err() to have more info why terminated
        return
    }
    // terminating handler terminates Server transaction automaticaly
})

The select at the end matters. The ACK for a 2xx response arrives as a separate request rather than through the transaction, and the documentation notes that a terminating handler terminates the server transaction automatically, while tx.Err() carries more information about why a transaction ended. If you do not need the transaction machinery, the stateless path is there, where the server writes a response directly with srv.WriteResponse(res), and there is also a simpler client call, client.Do(req), which waits for the final response in the same style as the standard library HTTP client. The default client.TransactionRequest builds the headers needed for a correct request, and options exist for the cases where you need to override them.

TLS is configured through the standard library

Transport security is handled by giving the server a tls.Config, and the README includes a complete helper that builds one from a certificate, a key and a set of root PEMs.

go
conf := generateTLSConfig(certFile, keyFile, rootPems)
srv.ListenAndServeTLS(ctx, "tcp", "127.0.0.1:5061", conf)
srv.ListenAndServeTLS(ctx, "ws", "127.0.0.1:5081", conf)

The helper is worth copying because it does the right things in the right order. It builds an x509.CertPool, appends the root certificates and fails loudly if the PEM cannot be parsed, it loads the key pair with tls.LoadX509KeyPair and returns the error rather than continuing with a broken config, and it sets both the certificate chain and the root authorities on the returned tls.Config. The same function serves the TCP and the WebSocket listeners, so a deployment terminates TLS once and reuses the config. The RFC 6026 support is listed in the stack, and the project also ships a SECURITY.md file for reporting issues. The Dockerfile in the repository is minimal, starting from golang:latest, copying the source and running go mod download, which suggests the container image is a build environment rather than a runtime packaging exercise.

The torture test status is an honest caveat

The README is unusually candid about conformance testing, and it is the part to read before depending on the parser for security sensitive work. The state of the RFC 4475 torture tests is tracked on a specific issue in the repository, and the note attached to it says that some strict validation things may be separated from parsing or may not be built into the library at all. That is the honest position of a library that optimizes for parsing speed: it accepts and produces messages quickly, and it does not attempt to reject every malformed input the standard describes. For a server sitting behind a trusted network, that trade-off is usually correct, since the parser is the hot path and malformed input is rare. For a SIP server exposed to the public internet, it is the thing to test yourself, and the torture test issue is where you would look for the current state. The performance claim in the README is framed the same careful way. It says the library was shown to excel in performance compared to some other configured base proxy solutions, and it points at the example/proxysip directory, which contains a simple stateful proxy used for stress testing with sipp, with the latest results published there. The claim is comparative and configuration dependent, so the sensible reading is that the example and the sipp tooling are there so you can reproduce the comparison on your own hardware.

Where sipgo is the wrong tool

Four boundaries stand out. The first is media. If what you are building needs to send and receive audio, sipgo alone gives you signalling, and the RTP side comes from diago, so a real call application pulls in two projects and you own the integration. The second is the level of abstraction. The API is transaction oriented and close to the wire, which is what you want for a proxy or a registrar and more than you want for a small business calling feature, so an application that just needs to place a call with a phone number is served better by a higher level framework sitting on top. The third is the parsing strictness, already noted, which is the reason to read the torture test issue before exposing a server directly. The fourth is the version churn. The release list shows v1.4.3 in July 2026, v1.5.0 in August and v1.6.0 in September, which is fast movement for a protocol library, and the coverage badge sits at 55.4 percent, so pinning a version and running your own conformance checks is more sensible than tracking the tip. On licensing, the project is BSD-2-Clause, which is permissive but still requires keeping the copyright notice when the library is redistributed.

Editorial conclusion

sipgo suits a Go team that needs a SIP server, registrar, proxy or client without writing a parser, and that is comfortable working at the transaction level rather than through a higher level framework. It does not suit a project that needs media handling in the same dependency, since the RTP stack lives in the separate diago project, and it does not suit someone who needs the RFC 4475 torture test cases to be enforced by the parser, since the documentation notes that some strict validation is separated from parsing. Before you build on it, run the example/proxysip proxy against sipp to see the behaviour you actually get, and note that the last push was on 2026-10-11 with the newest release at v1.6.0 from 2026-09-20.

Frequently asked questions

How do I add sipgo to a Go project?

Run go get github.com/emiago/sipgo. The module declares Go 1.23.0 with a toolchain of go1.24.2, and the documentation is published on the Go doc site for the module.

Which transports and RFCs does sipgo support?

The documentation marks UDP, TCP, TLS, WS and WSS as supported transports, and lists RFC 3261, RFC 3263, RFC 3581 and RFC 6026 for the stack.

Does sipgo handle media and RTP?

No. The media stack lives in the separate diago project, which the documentation describes as a full VOIP library and framework with a media stack, and call and media examples are in diago/examples.

How does sipgo handle RFC 4475 torture test inputs?

The state of the torture tests is tracked on an issue in the repository, and the note there says some strict validation things may be separated from parsing or not built into the library, so the parser is not a conformance firewall.

Official sources

  1. emiago/sipgo on GitHub
  2. Issues
  3. License: BSD-2-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/emiago-sipgo.svg)](https://hysenlabs.com/projects/emiago-sipgo)

Was this useful?