The problem has nothing to do with the file being external or embedded.
You've changed your code, and that's why it stopped working. You could keep your HTML code identical to the original one <h1 id="title" onclick="sayGoodbye();"> Hello </h1>.
Since you've changed, that's the reason it's not working anymore.
When adding event listeners via Javascript's addEventListener or jQuery's on, you must always pass a function reference, and never a function execution. If you use the (), the function will be called immediately, and the returned value of it will be passed to the other function you're calling.
For example, if I do that:
function num() {
  return 5;
}
console.log(num);   // it will log 'function num()'
console.log(num()); // it will log 5
Since your sayGoodbye function returns nothing, the value passed to jQuery's on will be undefined.
function num() {
   var y = 5;
   y = y + 10;
}
console.log(num);   // it will log 'function num() '
console.log(num()); // it will log 'undefined'
When you put in HTML onclick="sayGoodbye();", you're in fact saying:
Element.onclick = function() {
  sayGoodbye();
}
It automatically wrap your code into an anonymous function, and pass it as a reference to the event.
Tl;dr
Your code isn't working because you're using:
$('#title').on('click', sayGoodbye());
Which evaluates/calls the function immediately, because the parenthesis are present. 
Instead, you should supply a callback function by name:
$('#title').on('click', sayGoodbye);
Or as an anonymous function:
$('#title').on('click', function() {
  sayGoodbye();
});
Update
After you changing the question, it seems to be another simple problem.
Where are you defining your <script src="externalscript.js"></script>?
You should be placing it at the same place where your embedded one was. If you've put it into the head tag, it will be evaluated even before your h1#test is loaded, and then, it won't find the Element, and don't add the event listener to it.
But if you really want/need to put it inside the head tag, then, you must wrap your code into an load event listener. If using vanilla Javascript, it would be:
document.addEventListener('DOMContentLoaded', function() {
  // your code
});
But since you're using jQuery, you can use $(document).ready:
$(document).ready(function() {
  // your code
});
And there is a shorthand for it, that's just wrap into $();:
$(function() {
  // your code
});
Note: I've just realised that your biggest problem here is that you don't understand the difference between doing <h1 id="title" onclick="sayGoodbye();"> Hello </h1>, and adding your event handler through jQuery.
Your first example is working the way you expected, because when you do:
$("#title").html("Goodbye");
$("#title").click(function() {
  $("#title").html("Hello");
  $("#title").off("click");
});
You're saying:
- Change the innerHTMLproperty of the element with idtitleto 'Goodbye'
- Add a - clickevent handler that:
 - 2.1 Change the - innerHTMLproperty of the element with id- titleto 'Hello'
 - 2.2 Remove all the jQuery event handlers for the event - click
 
So, what is going to happen:
- When you click the first time - 1.1 Your - h1will change to 'Goodbye'
 - 1.2 And there will be a new - clickevent handler for it
 
- When you click the second time - 2.1 It will fire the same event again firstly, so your - h1will change to 'Goodbye'
 - 2.2 There will be a new - clickevent handler for it
 - 2.3 The previously event handler added on - 1.2will be fired
 - 2.4 Then, your - h1will change back to- Hello
 - 2.5 And you remove all the jQuery - clickevent handlers of it
 
So, when you click the third time, it will be like it was when the page was loaded, because the event handler placed into the HTML code will still remains, but all the jQuery event handlers that were attached to the h1 were removed.
That said, 3rd will be the same as 1st, 4th will be the same as 2nd, and so on.
When you stop attaching the event handler into your HTML code, and start attaching it through jQuery, your code stops working because:
- When you click the first time - 1.1 Your - h1will change to 'Goodbye'
 - 1.2 And there will be a new - clickevent handler for it
 
- When you click the second time - 2.1 It will fire the same event again firstly, so your - h1will change to 'Goodbye'
 - 2.2 There will be a new - clickevent handler for it
 - 2.3 The previously event handler added on - 1.2will be fired
 - 2.4 Then, your - h1will change back to- Hello
 - 2.5 And you remove all the - clickevent handlers of it
 
As you can see, pretty the same as the example above. But there is one big difference: When 2.5 happens, you don't have any click event handlers attached to the h1 anymore, since you've removed all of them there.
That happens, because when you pass only one parameter to the jQuery's off method, you're saying "Remove all the jQuery's event handlers for that type of event" (in that case, click). So, it will remove both the event handler attached to sayGoodBye and the one that you've created with the anonymous function.
In the first example, it doesn't happen, because jQuery doesn't know about the event you've attached via HTML code, so it never removes it, and your code works the way you expect.
So, if you want to work with jQuery only, your code should be something like:
$(function(){
  function sayGoodbye() {
    $('#title')
      .html('Goodbye')
      .off('click', sayGoodbye)
      .on('click', sayHello);
  }
  function sayHello() {
    $('#title')
      .html('Hello')
      .off('click', sayHello)
      .on('click', sayGoodbye);
  }
  $('#title').on('click', sayGoodbye);
});