Skip to content
STIR/SHAKEN Compliance for SIP Trunking Providers
SIP

STIR/SHAKEN Compliance for SIP Trunking Providers

Implement STIR/SHAKEN caller ID authentication for SIP trunking: PASSporT token generation, attestation levels, STI-SP certificate management, and SHAKEN verification flow.

Tumarm Engineering8 min read

STIR/SHAKEN Compliance for SIP Trunking Providers

STIR/SHAKEN is the FCC-mandated caller ID authentication framework that SIP trunking providers must implement. STIR (Secure Telephone Identity Revisited) defines the cryptographic mechanism. SHAKEN (Signature-based Handling of Asserted information using toKENs) is the industry profile that specifies how US voice providers apply it. Since June 2021, major carriers require originating providers to sign calls or risk having traffic flagged as unverified — meaning customer calls display "Spam Likely" or get blocked by call-screening apps.

This post covers what SIP trunking providers need to implement: STI-SP certificate procurement, PASSporT generation, Identity header construction, and the verification flow on the terminating side.

STIR/SHAKEN Architecture

Originating SP          Intermediate              Terminating SP
(your platform)         (optional)                (PSTN carrier)

    SIP INVITE ──────────────────────────────────────► SIP INVITE
    + Identity header                                   + Identity header
    (signed PASSporT)                                   (verified by carrier)
           │
           │ signs using
           │
    STI-CA certificate
    (from approved CA list)

The originating provider signs the call with an EC (Elliptic Curve) private key. The corresponding certificate, issued by an FCC-authorized STI-CA, is published at a public HTTPS URL. The terminating provider fetches the certificate and verifies the signature.

Attestation Levels

The Identity header includes an attestation level that signals how well the originating provider verified the caller:

LevelCodeMeaning
Full AttestationAYou know the customer and the number is authorized to them
Partial AttestationBYou know the customer but cannot confirm they own the number
Gateway AttestationCYou authenticated the source but have no customer relationship

Use attestation A whenever possible. Carriers downgrade or reject calls with level C from unknown providers. If you're a transit carrier passing calls from unknown sources, C is appropriate, but calls will display as unverified to end users.

Certificate Procurement

You need an STI-SP certificate from an FCC-authorized STI-CA. As of 2025, authorized CAs include:

  • Comodo/Sectigo STI-CA
  • TransNexus
  • ATIS STI-CA
  • Neustar

The certificate is a standard X.509 cert with EC P-256 key and an extension that identifies it as an STI certificate. The CERT URL (where you publish it) must be reachable via HTTPS from any carrier's verification systems.

# Generate EC P-256 key pair
openssl ecparam -genkey -name prime256v1 -noout -out sti-private.key
openssl ec -in sti-private.key -pubout -out sti-public.key

# Create CSR for the STI-CA
openssl req -new -key sti-private.key -out sti-request.csr \
  -subj "/C=US/O=Your Company/CN=sti.yourcompany.com"

# Submit CSR to your chosen STI-CA
# They issue the certificate and you publish it at a known HTTPS URL:
# https://cert.yourcompany.com/sti-cert.pem

PASSporT Token Structure

A PASSporT (Personal Assertion Token) is a JWT signed with your STI key. The structure:

// Header
{
  "alg": "ES256",
  "typ": "passport",
  "ppt": "shaken",
  "x5u": "https://cert.yourcompany.com/sti-cert.pem"
}

// Payload
{
  "attest": "A",
  "dest": {
    "tn": ["12025551234"]
  },
  "iat": 1701388800,
  "orig": {
    "tn": "14085559876"
  },
  "origid": "550e8400-e29b-41d4-a716-446655440000"
}

Key fields:

  • attest — attestation level (A, B, or C)
  • dest.tn — destination telephone number in E.164 without the +
  • orig.tn — originating telephone number in E.164 without the +
  • iat — Unix timestamp of call origination (must be within 60 seconds of current time)
  • origid — unique UUID per call for replay detection

Generating the Identity Header

import jwt
import time
import uuid
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

def generate_identity_header(orig_number, dest_number, attestation='A'):
    # Load private key
    with open('/etc/sti/private.key', 'rb') as f:
        private_key = serialization.load_pem_private_key(
            f.read(),
            password=None,
            backend=default_backend()
        )
    
    header = {
        'alg': 'ES256',
        'typ': 'passport',
        'ppt': 'shaken',
        'x5u': 'https://cert.yourcompany.com/sti-cert.pem'
    }
    
    payload = {
        'attest': attestation,
        'dest': {'tn': [dest_number.lstrip('+')]},
        'iat': int(time.time()),
        'orig': {'tn': orig_number.lstrip('+')},
        'origid': str(uuid.uuid4())
    }
    
    token = jwt.encode(
        payload,
        private_key,
        algorithm='ES256',
        headers=header
    )
    
    # SIP Identity header format
    return f'{token};info=<https://cert.yourcompany.com/sti-cert.pem>;alg=ES256;ppt="shaken"'

# Use in SIP INVITE
identity_header = generate_identity_header('+14085559876', '+12025551234', 'A')

Integration with Kamailio

Add the Identity header to outbound INVITEs in Kamailio using a Lua script that calls the signing service:

loadmodule "app_lua.so"
modparam("app_lua", "load", "/etc/kamailio/stir.lua")

request_route {
    if (is_method("INVITE") && !has_totag()) {
        lua_run("sign_call");
    }
    t_relay();
}
-- /etc/kamailio/stir.lua
function sign_call()
    local orig = KSR.pv.get("$fU")
    local dest = KSR.pv.get("$rU")
    
    -- Call local signing microservice
    local http = require("socket.http")
    local json = require("cjson")
    
    local body = json.encode({orig = orig, dest = dest, attest = "A"})
    local response, status = http.request(
        "http://127.0.0.1:8080/sign",
        body
    )
    
    if status == 200 then
        local result = json.decode(response)
        KSR.hdr.append("Identity: " .. result.identity_header .. "\r\n")
    else
        KSR.log("err", "STIR signing failed: " .. tostring(status))
    end
end

Run the signing microservice as a local sidecar process. Keep it on localhost to avoid network latency on the signing path — adding 100ms to call setup time for every INVITE is unacceptable at scale.

Verification on the Terminating Side

If you also receive calls from other carriers, verify incoming Identity headers:

import jwt
import requests
import time
from cryptography.x509 import load_pem_x509_certificate

def verify_identity_header(identity_header, orig_number, dest_number):
    # Parse the header: token;info=<cert_url>;alg=ES256;ppt="shaken"
    parts = identity_header.split(';')
    token = parts[0].strip()
    cert_url = parts[1].replace('info=<', '').replace('>', '').strip()
    
    # Fetch the signing certificate
    cert_response = requests.get(cert_url, timeout=2)
    cert = load_pem_x509_certificate(cert_response.content)
    public_key = cert.public_key()
    
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=['ES256'],
            options={'verify_exp': False}
        )
    except jwt.InvalidSignatureError:
        return {'valid': False, 'reason': 'Invalid signature'}
    
    # Validate payload claims
    if abs(time.time() - payload['iat']) > 60:
        return {'valid': False, 'reason': 'Token expired'}
    
    if payload['orig']['tn'] != orig_number.lstrip('+'):
        return {'valid': False, 'reason': 'Orig number mismatch'}
    
    return {
        'valid': True,
        'attestation': payload['attest'],
        'origid': payload['origid']
    }

Cache certificate fetches with a 1-hour TTL. The STI-CA certificate changes rarely, and fetching it on every call adds latency and creates a dependency on an external HTTPS endpoint in the call path.

FCC Compliance Timeline

RequirementDeadlineWho
Implement STIR/SHAKENJune 2021Major voice providers
STIR/SHAKEN or robocall mitigationJune 2022Small voice providers
Certificate must be from authorized STI-CAOngoingAll signing providers
Annual certification filingAnnuallyAll providers

File your annual certification at the FCC's Robocall Mitigation Database. Failure to file results in downstream carriers refusing to accept your traffic — a business-ending consequence for a SIP trunking provider.

stir-shakensip-trunkingcaller-idrobocallcompliancejwt
Benchmark
BALI Pvt.Ltd
Brave BPO
Wave
SmartBrains BPO

Ready to build on carrier-grade voice?

Talk to a VoIP engineer — not a salesperson.

Schedule a Technical Call →