|Fundamentals

Anatomy of a SIP Message: Reading the Headers

A line-by-line walkthrough of a real SIP INVITE message, explaining what every major header does and how requests and responses relate to each other.

SIP Signaling From the Wire Up: Part 2 of 12

In the last post we established what SIP does at a high level: it handles the signaling that sets up, modifies, and tears down communication sessions. Now we're going to get our hands on an actual SIP message and go through it piece by piece. By the end of this post, you'll be able to look at a SIP message in a packet capture or a debug log and understand what every major header is doing.

The structure of a SIP message

Every SIP message has the same basic structure: a start line, a set of headers, an empty line, and an optional body.

Start Line
Header: value
Header: value
Header: value

Body (optional)

The start line tells you what kind of message this is. The headers carry all the metadata about the message: who it's from, who it's going to, how to route it, how to track it. The body, when present, usually contains SDP (Session Description Protocol) data that describes the media session. We'll cover SDP in depth in post 5.

SIP messages come in two flavors: requests and responses. A request is something one party is asking another to do. A response is the answer to that request.

Requests and responses

The start line of a request is called the Request-Line. It contains a method name (the action being requested), a Request-URI (where the request is going), and the SIP version:

INVITE sip:[email protected] SIP/2.0

The method tells you what action is being performed. The most common methods are:

INVITE starts a new call or modifies an existing one. ACK confirms that you received a final response to an INVITE. BYE ends a call. CANCEL cancels a pending INVITE (the phone is ringing but the caller hung up before the callee answered). REGISTER tells the registrar where you are. OPTIONS asks about capabilities (often used as a keep alive or health check).

The start line of a response is called the Status-Line. It contains the SIP version, a numeric status code, and a human readable reason phrase:

SIP/2.0 200 OK

We'll cover response codes in detail in post 7. For now, just know that they work like HTTP: 1xx means provisional (in progress), 2xx means success, 3xx means redirection, 4xx means the request had a problem, 5xx means the server had a problem, and 6xx means a global failure.

Taking apart a real INVITE

Let's look at a complete INVITE message, the kind you'd see in a packet capture when someone places a call. We'll go through each header and explain what it's doing and why it matters.

INVITE sip:[email protected]:5060 SIP/2.0
Via: SIP/2.0/UDP 10.0.0.50:5060;branch=z9hG4bK776asdhds
Max-Forwards: 70
To: "Bob Smith" <sip:[email protected]>
From: "Alice Jones" <sip:[email protected]>;tag=1928301774
Call-ID: [email protected]
CSeq: 1 INVITE
Contact: <sip:[email protected]:5060>
Content-Type: application/sdp
Content-Length: 256

Let's take these one at a time.

The Request-Line

INVITE sip:[email protected]:5060 SIP/2.0

This tells us three things. The method is INVITE, meaning this is a request to start a call. The Request-URI is sip:[email protected]:5060, which is the address the request is being sent to. And the SIP version is 2.0, which is the only version you'll encounter in practice.

The Request-URI is the address that controls routing. Proxy servers use this field to decide where to forward the message. As the message passes through proxies, the Request-URI may be rewritten to point to the next hop. The Request-URI in this example points to a specific IP address and port, which suggests this INVITE has already been routed and is heading directly to Bob's phone.

Via

Via: SIP/2.0/UDP 10.0.0.50:5060;branch=z9hG4bK776asdhds

The Via header records the path the request has taken. Each server that handles the message adds its own Via header to the top of the stack. When the response comes back, each server peels off its Via header to route the response back along the exact same path.

This header tells us that the message was sent via UDP from 10.0.0.50 on port 5060. The transport could also be TCP, TLS, or WSS — browser-based SIP clients carry the same messages over WebSocket connections. The branch parameter is a unique transaction identifier. Every new SIP transaction gets a new branch value. If you're looking at a capture with many calls happening simultaneously, the branch value is one way to track which responses belong to which requests.

In a real world call that passes through one or more proxies, you'd see multiple Via headers stacked up, one for each hop. The topmost Via is always the server that handled the message most recently. The bottom Via is the original sender.

Max-Forwards

Max-Forwards: 70

This is a loop prevention mechanism. Every proxy that forwards the message decrements this value by one. If it reaches zero, the message is discarded and a 483 Too Many Hops response is sent back. The default starting value is 70, which is more than enough for any legitimate routing path. If you see a 483 response in a trace, you've got a routing loop somewhere in your infrastructure.

To

To: "Bob Smith" <sip:[email protected]>

The To header identifies the logical recipient of the request. Here's the counterintuitive part: the To header is not used for routing. The Request-URI handles routing. The To header is an identifier that stays constant throughout the dialog. Once the callee responds, a tag parameter gets added to the To header, and the combination of Call-ID, From tag, and To tag uniquely identifies this specific call dialog.

The display name ("Bob Smith") is purely informational. It's what might show up on a screen somewhere, but it has no protocol significance.

From

From: "Alice Jones" <sip:[email protected]>;tag=1928301774

The From header identifies the logical sender. Like the To header, it's an identifier, not a routing directive. The tag parameter is generated by the sender and is unique for this call. Combined with the Call-ID and the To tag (added later by the callee), these three values create a globally unique dialog identifier.

An important note: the From header is not a reliable indicator of the caller's actual identity. It can be set to anything. This is why caller ID spoofing is possible. The From header might say one thing while the actual source of the call is something entirely different. We'll touch on this more in the security post later in the series.

Call-ID

Call-ID: [email protected]

The Call-ID is a globally unique identifier for this call. It's generated by the calling party and stays the same for every SIP message related to this call, from the initial INVITE through every provisional response, the final response, the ACK, and eventually the BYE. When you're reading a packet capture with dozens of calls happening simultaneously, Call-ID is how you filter down to the specific call you're investigating.

The format is typically a random string followed by @ and the sender's host. Different implementations generate the random portion differently, but the goal is uniqueness.

CSeq

CSeq: 1 INVITE

CSeq stands for Command Sequence. It contains a sequence number and the method name. The sequence number can start at any value (per RFC 3261, the initial value is arbitrary), and it increments with each new request within a dialog. If the call is later modified with a re-INVITE, the CSeq number increments. A subsequent BYE increments it again.

CSeq serves two purposes. First, it lets the recipient match a response to its request. A response to the INVITE will have the same CSeq value and method. A response to a later BYE will carry the BYE's CSeq value. Second, it provides ordering. If two requests arrive out of order, CSeq tells the recipient which one came first.

Contact

Contact: <sip:[email protected]:5060>

The Contact header contains the address where the sender wants to receive subsequent requests within this dialog. This is different from both the From header and the Via header. The From header is a logical identity. The Via header is for routing responses to this specific request. The Contact header is for routing future requests in the ongoing dialog.

This is the header that causes a lot of NAT problems. The phone sets its Contact address to its local IP, which might be a private address like 10.0.0.50. The remote end sees that address and tries to send subsequent messages to it, which fails if the phone is behind NAT and the private address isn't reachable from the outside. We'll cover this in depth in the NAT post.

Content-Type and Content-Length

Content-Type: application/sdp
Content-Length: 256

These headers describe the message body. Content-Type tells us the body is SDP, which is by far the most common body type in SIP messages. Content-Length tells us how many bytes the body contains. Not every SIP message has a body. A BYE, for example, typically doesn't include SDP because there's no media negotiation happening. An INVITE almost always includes SDP because it needs to propose the terms of the media session.

How a response mirrors the request

When Bob's phone receives this INVITE and starts ringing, it sends back a 180 Ringing response. The response looks like this:

SIP/2.0 180 Ringing
Via: SIP/2.0/UDP 10.0.0.50:5060;branch=z9hG4bK776asdhds
To: "Bob Smith" <sip:[email protected]>;tag=a6c85cf
From: "Alice Jones" <sip:[email protected]>;tag=1928301774
Call-ID: [email protected]
CSeq: 1 INVITE
Contact: <sip:[email protected]:5060>
Content-Length: 0

Notice that the Via, From, Call-ID, and CSeq are identical to the request. This is how you match a response to the request that triggered it. The To header now has a tag added by Bob's phone (tag=a6c85cf). The Contact header shows Bob's address. And the Content-Length is 0 because a 180 Ringing doesn't carry an SDP body.

The dialog identifier

At this point, with both the From tag from Alice and the To tag from Bob, plus the Call-ID, we have a complete dialog identifier:

This combination uniquely identifies this specific call dialog. Every subsequent message between Alice and Bob for this call will carry these same three values. If you're searching through a capture for all the messages related to a single call, these three fields are your filter.

What this looks like in practice

In a debug log or a Wireshark capture, SIP messages appear as blocks of text. Being able to scan the first line (request or response), check the Call-ID (which call is this), check the CSeq (what request is this related to), and glance at the Via headers (what path did this take) is the fundamental skill of SIP troubleshooting. If you have a capture file and want to skip the manual parsing, the SIP call stories tool will walk through the flow for you. Everything else builds on this ability to orient yourself within a SIP message and understand what you're looking at.

Now that you can read individual messages, the next post puts them in sequence and walks through a complete call from start to finish.

Next up: Your First Call Flow: INVITE to BYE, which follows a complete call through every SIP message from setup to hangup.

sipsip-headersvoipprotocol-fundamentals

Share

Opens your messaging app. We do not collect or store any phone numbers.
Opens your email client. We do not collect or store any email addresses through sharing.

Want to know when we publish new articles? Sign up for updates