Using $$token = SHA256(APIKey \mathbin\| SecretKey \mathbin\| expireTimestamp) \mathbin\| expireTimestamp$$
is not secure since it can cause a length extension attack as;
$$token2 = SHA256(APIKey \mathbin\| SecretKey \mathbin\| expireTimestamp \| \color{red}{extension}) \mathbin\| (expireTimestamp \| \color{red}{extension}) $$
This is a valid token for the forgery.
NIST on call to SHA3 candidates required to be resistant to length-extension
attacks. Keccak now named SHA3 as the winner and Blake2 is still a good alternative from the competition. Both are resistant to length extension attacks, Keccak by using the capacity and Blake2 is using HAIFA construction, are secure to length extension attacks.
To mitiage one can use HMAC;
$$token = \operatorname{HMAC-SHA256}(privateKey, publicKey \mathbin\| expireTimestamp) \mathbin\| expireTimestamp$$ Note that this will call the SHA-256 at least two times ( three times if the key is longer than the block size of the hash function, 512-bit for SHA-256).
Instead of HMAC, we can use BLAKE2. BLAKE2 is very fast and even we have a parallel version BLAKE3 and using
$$token = \operatorname{BLAKE2}( publicKey \mathbin\| privateKey \mathbin\|expireTimestamp) \mathbin\| expireTimestamp$$ is secure.
NIST also standardized a MAC for SHA3 named KMAC( or here).
Both BLAKE2 and KMAC are preferable to HMAC.
If you insist to use SHA2 with a 256-bit output size then use SHA-512/256 which is the truncated version of SHA-512/256 with different initial values to separate the domains. SHA-512/256 is immune to length extension attacks and it is 64-bit CPU friendly by design.