You can terminate a solve from a callback by returning a non-zero status from the callback or setting the useraction_p argument to CPX_CALLBACK_FAIL in case you are using the callable library. For the object oriented APIs just invoke the callback's abort() method. The drawback of this is that optimization stops immediately, hence the incumbent will be discarded. You can work around this by storing away the incumbent yourself in the callback.
Another option would be to add another callback, for example a branch callback. In the lazy constraint callback, when you recognize that you can stop optimization just set a (global) flag. In the branch callback test this global flag and if it is set abort optimization as described above.
The reason why adding "objective <= incumbentObjective" does not work is this: as soon as you add a constraint from the callback CPLEX assumes the current solution is infeasible and discards it. CPLEX does not check whether the constraint is redundant and does not actually cut off the solution (that is your job :-) )
#CPLEXOptimizers#DecisionOptimization