FreePBX attempts to look at the DIALSTATUS and possibly the HANGUPCAUSE to determine what to do.
A trunk will not failover if the “conclusive” interpretation at the Asterisk level is that the end party can’t be reached.
An example of this is if it gets back the equivalent of a “BUSY” which means “this trunk got to them and they said they are busy” thus don’t keep trying other trunks just to hear the same news. There are other scenarios, such as I believe reporting it’s an invalid number.
The cases where it will try another trunk is when it gets back a report that “the trunk could not complete the call” but nothing conclusive about the status of the final destination since it could not get that far. This is the case when a trunk is CONGESTED for example, e.g. max channels are used up.
The problem is two fold. The SIP response codes are open to interpretation to begin with and don’t always map cleaning to HANGUPCAUSE codes, which are the codes that are better defined in PRIs for example and what gets mapped to the HANGUPCAUSE. The second issue is that some providers make poor choices or outright incorrect choices as to what to return in their SIP response code.
We’ve tried to get more granular over time by looking at the HANGUPCAUSE. We used to JUST look at the DIALSTATUS and if it was CONGESTION we gave up.
On modern versions of FreePBX (2.9 for example but probably still the case for 2.8), according the a quick web search (which may be wrong), 603 translates to a HANGUPCAUSE of 21. According to a quick check of the dialplan generated, 21 falls into the “_X.” bucket of hangup causes which continues to the next trunk.
So, either you have a dated (and/or unsupported) version of FreePBX (you didn’t mention what you were running which always helps), or a different HANGUPCAUSE is being returned. If you have a modern supported version of FreePBX, post what your HANGUPCAUSE is coming back as to see if there is an issue either in Asterisk or FreePBX.