It is entirely robust and safe - but not directly portable to the few devices around that have CHAR_BIT > 8. By using uint8_t in the code, if it is used on a device with CHAR_BIT > 8, then the compilation will fail because the type uint8_t does not exist. Your aim in writing code should be to make it clear, efficient on the targets that matter to you, and make it fail with a compile-time error on targets that don't match your requirements. So if your realistic targets have 8-bit char (and most do - the exceptions are almost exclusively DSPs), and your code can be better by relying on that feature, then you /should/ rely on 8-bit chars. And for robustness the code should fail to compile if that feature or assumption does not hold.
If you want to use the same technique and make it portable to 16-bit char devices (TMS320 and so on), then you can't use uint8_t types - uint16_t is the smallest you should use. And the portable static_assert should be:
static_assert((CHAR_BIT * sizeof(format_payload)) == 8 * 4);
That is "better" if you want longer, slower, uglier and hard to maintain code.
What you call "lazy", I call clear, neat and efficient.
It is /a/ way to do it in a portable manner - and sometimes you want extreme portability. But such portability is rarely useful, and rarely results in better code.
When you write the documentation for a project, do you assume anyone reading it is happy with English, or do you translate it into a few dozen other languages for "portability" ? Do you avoid sentences with more than 10 words because some people reading it might be severely dyslexic? And do you expect your clients to pay for the extra time needed for such "robustness" and "portability" ?
I am not saying that portability is a bad thing, or that your method is necessarily a poor choice. I am merely saying that "portability" is not free, and you should not pay more of it than you actually need.