DTLS Handshake Never Starts - Mediasoup - WhatsApp Business API Integration

Hi MediaSoup team! I need clarification on DTLS role negotiation.

The Issue

My WebRTC transport’s DTLS handshake never starts when connecting with WhatsApp Business API. Transport state stays dtlsState: 'new' indefinitely, even though network is confirmed working.

Setup

  • MediaSoup: v3.x on AWS EC2 (public IP, UDP ports open)
  • Remote Peer: WhatsApp Business API servers
  • Network: Verified reachable with tcpdump - but NO packets arrive

The Negotiation

WhatsApp’s Offer:

a=setup:actpass
a=fingerprint:sha-256 XX:XX:...

My Answer:

a=setup:passive
a=ice-lite
a=fingerprint:sha-256 YY:YY:...

My connect() call:

await whatsappTransport.connect({
  dtlsParameters: {
    role: 'client',  // ← Is this correct?
    fingerprints: [/* extracted from WhatsApp SDP */]
  }
});

// Result: dtlsState stays 'new' forever

My Question

When I answer a=setup:passive (I am DTLS server), should I pass:

  • role: 'client' ← Meaning “remote peer is DTLS client”?
  • OR role: 'server' ← Meaning something else?

My Understanding

I believe:

  • MediaSoup transports are always passive (cannot initiate DTLS)
  • When I answer passive, WhatsApp should initiate as DTLS client
  • So I should tell MediaSoup: role: 'client' (remote is client)

But no DTLS packets arrive from WhatsApp. Am I mapping the role incorrectly?

What I’ve Verified :white_check_mark:

  • UDP ports 10000-10100 are open (Security Group configured)
  • announcedIp matches actual public IP
  • ICE parameters are correct
  • Fingerprints extracted correctly from SDP
  • Transport creation succeeds
  • SDP answer is sent to WhatsApp successfully
  • tcpdump running - sees NO incoming packets

Logs

📷 Creating WhatsApp transport
✅ Transport created: dtlsState='new', iceState='new'
🔌 Connecting with role='client'
✅ Connect() completed
⏰ 10 seconds later: dtlsState still 'new'  ← Problem!

Additional Context

This works fine with browser-based WebRTC clients. Only WhatsApp Business API has this issue. Wondering if there’s something specific about WhatsApp that I’m missing, or if my DTLS role logic is wrong.

Any guidance would be greatly appreciated! :folded_hands:


Debug Info

transport.on('dtlsstatechange', (state) => {
  console.log(`DTLS: ${state}`);
  // Only ever logs: "new"
});

transport.on('icestatechange', (state) => {
  console.log(`ICE: ${state}`);
  // Only ever logs: "new"
});

TL;DR: When answering passive to actpass offer, what role should I pass to transport.connect()? Currently using 'client' but DTLS never starts.

DTLS happens after ICE. Do you see ICE/STUN packets using wireshark? No because both mediasoup and that’s WhatsApp server are ICE Lite AFAIR so they cannot communicate since both of them expect to receive ICE requests from the other.