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 
- UDP ports 10000-10100 are open (Security Group configured)
announcedIpmatches 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! ![]()
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.