Last updated: April 19, 2026

Perfect Negotiation is a recommended pattern for handling WebRTC renegotiation in a symmetric way, where either peer can start a renegotiation at any time without the two sides stepping on each other.

It is documented in the W3C WebRTC specification as the canonical way to use the negotiationneeded event together with setLocalDescription() and setRemoteDescription().

The problem Perfect Negotiation solves

Classic WebRTC signaling assumes one peer is the caller and the other is the callee. The caller creates the offer, the callee answers, and renegotiation follows the same direction.

That model breaks down as soon as both peers can change the session – adding a track, switching cameras, or triggering an ICE restart. If both sides happen to generate an offer at the exact same moment, the offers collide. This is known as glare in telecom terminology. Without a tie-breaker, you end up with:

  • Both peers in have-local-offer state, each waiting for the other to answer
  • Out-of-order SDP exchanges that leave the PeerConnection in an inconsistent state
  • Races where one peer’s changes overwrite the other’s

Perfect Negotiation gives each side a deterministic rule so the collision always resolves cleanly.

Polite and impolite peers

The core idea is to assign one peer as polite and the other as impolite. This is decided once at session setup (typically based on who initiated the call, or a random coin flip exchanged via signaling).

  • Impolite peer: Stands its ground. If an offer arrives while it already has a pending local offer, it ignores the incoming offer
  • Polite peer: Yields. If an offer arrives while it has a pending local offer, it rolls back its own offer and accepts the remote one

The roles are arbitrary but must be agreed in advance. As long as exactly one side is polite, glare is always resolved the same way.

How the pattern works

Perfect Negotiation ties together three WebRTC mechanisms:

  1. negotiationneeded event fires whenever the PeerConnection needs a new offer (a track was added, ICE restart was requested, etc.)
  2. Rollback is a special SDP type ({type: 'rollback'}) that cancels a pending local offer and returns the PeerConnection to stable
  3. Offer/answer collision detection happens inside the handler for incoming offers – the polite peer checks whether it has a pending local offer and rolls back if so

The handler does not need to know *why* renegotiation is happening – adding a track, removing a track, or restarting ICE all flow through the same path.

Perfect Negotiation and ICE restart

ICE restart is one of the cleanest examples of where Perfect Negotiation pays off. Calling pc.restartIce() fires negotiationneeded, which then runs through the same offer/answer flow as any other change. The polite/impolite rules handle the case where the other side is also renegotiating at the same moment – for example, adding a track while you are trying to recover connectivity.

That is why restartIce() is preferred over the older createOffer({ iceRestart: true }) call: it integrates naturally with the Perfect Negotiation handler instead of requiring a separate code path.

When Perfect Negotiation is worth adopting

Perfect Negotiation adds some signaling complexity, so it is overkill for strictly one-sided flows (classic caller/callee with no dynamic track changes).

It becomes valuable when:

  • Either peer can add or remove tracks mid-call (screen sharing toggles, camera switches)
  • You want automatic ICE restart on connectivity failure
  • Your signaling channel is unreliable or out-of-order
  • You are building a peer-to-peer application without a central server dictating who leads

For applications that fit this profile, Perfect Negotiation fits well in resolving signaling edge cases.

Additional reading

Tags:

Looking to learn more about WebRTC? 

Check my WebRTC training courses

About WebRTC Glossary

The WebRTC Glossary is an ongoing project where users can learn more about WebRTC related terms. It is maintained by Tsahi Levent-Levi of BlogGeek.me.