After further testing I realized that I should be posting to the channel ID rather than the whisper channel.
Thanks @Breci for your help pointing me in the right direction.
Here is the working code:
{
...
"role": "external",
"channel_id": decodedToken.channel_id,
"pubsub_perms": { "send":[ "whisper-*" ] }
};
And in the body:
{
....
"targets": [ "whisper-" + decodedToken.opaque_user_id ]
}
Posting to:
'https://api.twitch.tv/extensions/message/' + decodedToken.channel_id