Your design is that the server has a key (which you call the salt, but would have to be secret here) and generates the client's public and secret values using e.g.:
$$p = ID\\
s = H(k|p),$$
where $H$ is some hash function. This seems secure: if an attacker only has access to $p$ they have no chance of generating $s$ without knowing $k$.
However, if $H$ is susceptible to a length extension attack, you would have to make sure all $p$ values are the same size. Therefore, and in case there are other weaknesses in the hash function, it would be better to use HMAC:
$$s = \operatorname{HMAC}_H(k, p)$$
As a MAC it is designed for this kind of purpose. You would have to keep $k$ secret, since an attacker knowing it could generate any $s_i$ from the corresponding $p_i$. Also, an attacker knowing some pair $(p', s')$ would be able to test guesses for $k$ offline so it would need to be a strong key.
There's also the alternative construction where you pick a random secret value and use HMAC to generate the public ID:
$$s = r\\
p = \operatorname{HMAC}_H(s, k)$$
The advantage here is that an attacker would be unable to generate the $s$ even if they did know $k$ – i.e. you would be able to make it a public salt rather than a key. (Attackers knowing it would be able to generate their own $(p, s)$ pairs, though.) In that case you should, however, invert the argument order and use $s$ as the HMAC key and $k$ as the message (as above), because HMAC security relies on the key being secret.
The downside is that your IDs would be random numbers (or bitstrings). You couldn't use a chosen public ID like a username, email address or a small integer. Each $s$ would also have to be a strong key, so you would need to generate more strong random numbers than in the first alternative.