Mergesort Criticism in C
Avoid code duplication
Your code is more verbose than necessary, because you are repeating a lot of things unnecessarily, or write things in a more complex way than necessary. For example:
(index + (index + length)) / 2

This looks really weird, and is actually equivalent to:
index + length / 2

Which makes much more sense. Even with that change, that expression is used a lot in the code, so it's best to give it a name:
uint32_t middle = index + length / 2;

You also duplicated a call to mergesort_helper() in order to handle odd and even cases of length. However, you don't have to if you write it like so:
uint32_t middle = index + length / 2;
uint32_t end = index + length;

mergesort_helper(array, index, middle - index, size_element, compare, storage);
mergesort_helper(array, middle, end - middle, size_element, compare, storage);

You might also want to consider renaming index to start.
Use const for pointer arguments in compare()
Since the function compare() should not modify the elements it is comparing, the arguments should be pointers to const:
int32_t compare(const void *ptr1, const void *ptr2) {
   const int32_t *temp_ptr1 = ptr1;
   const int32_t *temp_ptr2 = ptr2;
   ...
}

Optimizing the while-loop
There are a few minor changes that can be made to speed up the while-loop a little bit. First, calling compare() is probably going to be the most expensive operation, so you would want to avoid calling it if possible. Rearrange the order in which you check things to make use of short-circuiting:
if(i &lt; middle &amp;&amp; (j &gt;= end || compare(...)))

In the above, compare() will only be called if i &lt; middle is true and j &gt;= end is false.
Another possible optimization is realizing that if i &gt;= middle || j &gt;= end, you know you only need to copy the remainder of one side of the midpoint. Instead of doing that element by element, you could do that in one large memcpy(). It also simplifies the if-statements in the while-loop, like so:
while (i &lt; middle &amp;&amp; j &lt; end) {
    if (compare(array + i * size_element, array + j * size_element) &lt;= 0) {
        memcpy(storage + k * size_element, array + i * size_element, size_element);
        i++;
    } else {
        memcpy(storage + k * size_element, array + j * size_element, size_element);
        j++;
    }

    k++;
}

/* Copy the remainder */
if (i &lt; middle) {
    memcpy(storage + k * size_element, array + i * size_element, size_element * (middle - i));
} else {
    memcpy(storage + k * size_element, array + j * size_element, size_element * (end - j));
}

The above has also the advantage that compare() is only called once every iteration, whereas in your original code it could have been called twice.
Avoid void pointer arithmetic
Lau G mentioned void pointers, and indeed doing arithmetic on void pointers is illegal. The simple workaround is to cast it to a char * first:
static void mergesort_helper(void *array_ptr, ...)
{
    char *array = array_ptr;
    ...

To catch these sort of issues, enable strict compiler warnings. For GCC and Clang, use -Wall -pedantic. Fix all the warnings the compiler produces.