Manual Rewards: Developer Guide and Hooks Reference

The Manual Reward type lets you advertise a perk on a rank tier that you, the store owner, fulfil yourself. Examples include a hand-written thank-you card, a free consultation, a physical gift mailed by your team, or a credit applied in a separate billing system. The plugin records that the reward was earned and fires an action hook, but it does not deliver the reward automatically. This guide shows the available hooks and walks through the most common ways to wire manual rewards into your store with custom code.

What Happens When a Manual Reward Fires

When a customer reaches a rank that has an active manual reward configured:

  • The reward is logged in the plugin debug log.
  • The action wpgens_loyalty_rank_reward_issued is fired with the user, the reward, and the rank.
  • No email is sent to the customer.
  • The reward is not marked as claimed in the database, because the plugin has no way to know when you have actually delivered it.
  • The reward is shown in the rank card on the customer’s loyalty dashboard, but no Claim button appears (those are only rendered for points, discount, and shipping rewards).

Everything beyond the log entry is up to you. The hooks below are how you connect that signal to whatever delivery mechanism makes sense for your business.

Hooks Reference

wpgens_loyalty_rank_reward_issued

The primary action for manual reward delivery. Fired immediately after a manual reward is logged.

do_action('wpgens_loyalty_rank_reward_issued', $user_id, $reward, $rank, $message);
ArgumentTypeDescription
$user_idintWordPress user ID of the customer who earned the reward.
$rewardobjectReward record. Properties: id, title, type (will be manual), meta (JSON string), active.
$rankobjectRank record. Properties: id, name, slug, milestone, active.
$messagestringInternal log message. For manual rewards this is always Manual reward noted.

This hook also fires for points, discount, shipping, and multiplier rewards. Always check $reward->type when you only want to handle manual rewards.

wpgens_loyalty_user_rank_changed

Fires once after all rewards for the new rank have been processed. Useful when you want to react to the rank promotion as a whole rather than to each reward individually.

do_action('wpgens_loyalty_user_rank_changed', $user_id, $current_rank, $previous_rank, $issued_rewards);

Note: the $issued_rewards array does not include manual rewards. Manual rewards are tracked through wpgens_loyalty_rank_reward_issued only. To know which manual rewards a rank has, query them with WPGL_Ranks_Database::get_rank_rewards($current_rank->id, true).

How to Send a Custom Email When a Manual Reward Is Issued

This is the most common use case. Hook into wpgens_loyalty_rank_reward_issued, filter for the manual type, and send an email with wp_mail.

add_action('wpgens_loyalty_rank_reward_issued', function ($user_id, $reward, $rank, $message) {
    if ($reward->type !== 'manual') {
        return;
    }

    $user = get_userdata($user_id);
    if (!$user || !$user->user_email) {
        return;
    }

    $subject = sprintf('You unlocked a new perk: %s', $reward->title);
    $body = sprintf(
        "Hi %s,\n\nCongratulations on reaching the %s rank! You have unlocked: %s.\n\nOur team will be in touch shortly to arrange delivery.\n\nThanks,\nThe Team",
        $user->display_name,
        $rank->name,
        $reward->title
    );

    wp_mail($user->user_email, $subject, $body);
}, 10, 4);

How to Send the Email Only for a Specific Manual Reward

If you have multiple manual rewards across different ranks and only one of them should trigger an email, match by reward title or by a custom flag stored in the reward’s meta JSON.

add_action('wpgens_loyalty_rank_reward_issued', function ($user_id, $reward, $rank, $message) {
    if ($reward->type !== 'manual') {
        return;
    }

    // Match by exact title.
    if ($reward->title !== 'Free Consultation Call') {
        return;
    }

    // Send your custom email here.
    $user = get_userdata($user_id);
    wp_mail(
        $user->user_email,
        'Book your free consultation call',
        "Reply to this email and we will set up your call within 48 hours."
    );
}, 10, 4);

How to Notify Your Team in Slack or Microsoft Teams

Manual rewards usually require someone on your team to take action. Posting a message to a Slack or Teams webhook is the most reliable way to make sure nothing slips through.

add_action('wpgens_loyalty_rank_reward_issued', function ($user_id, $reward, $rank, $message) {
    if ($reward->type !== 'manual') {
        return;
    }

    $user = get_userdata($user_id);
    $payload = [
        'text' => sprintf(
            'Manual reward to fulfil: *%s* for *%s* (rank: %s, email: %s)',
            $reward->title,
            $user->display_name,
            $rank->name,
            $user->user_email
        ),
    ];

    wp_remote_post('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', [
        'headers' => ['Content-Type' => 'application/json'],
        'body'    => wp_json_encode($payload),
        'timeout' => 5,
        'blocking' => false, // fire and forget
    ]);
}, 10, 4);

How to Fire a Klaviyo or Mailchimp Event for a Manual Reward

If you want the manual reward to trigger a flow in your email automation platform, fire a custom event from the same hook. The example below uses Klaviyo, but the pattern is the same for any platform.

add_action('wpgens_loyalty_rank_reward_issued', function ($user_id, $reward, $rank, $message) {
    if ($reward->type !== 'manual') {
        return;
    }

    $user = get_userdata($user_id);
    $payload = [
        'data' => [
            'type' => 'event',
            'attributes' => [
                'metric'  => ['data' => ['type' => 'metric', 'attributes' => ['name' => 'Manual Reward Issued']]],
                'profile' => ['data' => ['type' => 'profile', 'attributes' => ['email' => $user->user_email]]],
                'properties' => [
                    'reward_title' => $reward->title,
                    'rank_name'    => $rank->name,
                ],
            ],
        ],
    ];

    wp_remote_post('https://a.klaviyo.com/api/events/', [
        'headers' => [
            'Authorization' => 'Klaviyo-API-Key YOUR-PRIVATE-KEY',
            'Content-Type'  => 'application/vnd.api+json',
            'Accept'        => 'application/vnd.api+json',
            'revision'      => '2024-05-15',
        ],
        'body'    => wp_json_encode($payload),
        'timeout' => 10,
    ]);
}, 10, 4);

How to Add a Custom Button to a Manual Reward in the Rewards List

By default, manual rewards appear in the rank card on the customer’s loyalty page as a title plus optional description, with no action button. To add your own button (for example, a Book my session link), override the rank template by copying it into your theme.

  1. Copy templates/points-ranks.php from the plugin into your theme at your-theme/wpgens-loyalty-program/points-ranks.php.
  2. Find the foreach ($rewards as $reward) loop inside .wpgens-loyalty-rewards-list.
  3. Add a conditional block that runs only when $reward->type === 'manual' and outputs a button or link.
<?php if ($reward->type === 'manual'): ?>
    <a class="wpgens-loyalty-button" href="<?php echo esc_url(home_url('/book-consultation/')); ?>">
        <?php echo esc_html__('Book Now', 'your-theme'); ?>
    </a>
<?php endif; ?>

If you want the button URL or label to differ per reward, store that information in the reward’s meta field as JSON when you create the reward in the admin, then read it back in the template:

<?php if ($reward->type === 'manual'):
    $meta = $reward->meta ? json_decode($reward->meta, true) : [];
    $url   = $meta['button_url']   ?? '';
    $label = $meta['button_label'] ?? __('Learn more', 'your-theme');
    if ($url): ?>
        <a class="wpgens-loyalty-button" href="<?php echo esc_url($url); ?>"><?php echo esc_html($label); ?></a>
    <?php endif;
endif; ?>

How to Track Fulfilment of a Manual Reward

Because the plugin does not know when you have delivered a manual reward, you may want to record fulfilment yourself so you can mark them as done in the admin. The simplest pattern is a per-user meta key.

// Mark a manual reward as pending when issued.
add_action('wpgens_loyalty_rank_reward_issued', function ($user_id, $reward, $rank, $message) {
    if ($reward->type !== 'manual') {
        return;
    }

    $pending = get_user_meta($user_id, '_my_manual_rewards_pending', true) ?: [];
    $pending[] = [
        'reward_id'    => $reward->id,
        'reward_title' => $reward->title,
        'rank_id'      => $rank->id,
        'rank_name'    => $rank->name,
        'issued_at'    => current_time('mysql'),
        'fulfilled'    => false,
    ];
    update_user_meta($user_id, '_my_manual_rewards_pending', $pending);
}, 10, 4);

You can then build a small admin page that lists pending rewards across all users and lets your team flip the fulfilled flag once the reward has been delivered.

How to React to All Manual Rewards on a Rank Promotion

If a single rank has multiple manual rewards and you want to bundle them into one notification rather than one per reward, react to wpgens_loyalty_user_rank_changed instead and query the rank’s rewards yourself.

add_action('wpgens_loyalty_user_rank_changed', function ($user_id, $current_rank, $previous_rank, $issued_rewards = []) {
    if (!$current_rank) {
        return;
    }

    $rewards = WPGL_Ranks_Database::get_rank_rewards($current_rank->id, true);
    $manual = array_filter($rewards, fn($r) => $r->type === 'manual');

    if (empty($manual)) {
        return;
    }

    $titles = wp_list_pluck($manual, 'title');
    $user   = get_userdata($user_id);

    wp_mail(
        $user->user_email,
        sprintf('Welcome to %s rank — your perks', $current_rank->name),
        "You unlocked:\n- " . implode("\n- ", $titles)
    );
}, 10, 4);

Storing Custom Data on a Manual Reward

The reward’s meta column is a JSON string. The admin UI uses it for built-in fields like description, but you can put any extra keys there to drive your custom logic. Read it back with json_decode($reward->meta, true) and use whatever keys you defined.

Common patterns include storing a destination URL for a button, a SKU for a physical item, an internal SLA in days, or a tag that another plugin reads. Because the column is opaque JSON, none of these custom keys interfere with the plugin’s own behaviour.

Browse our plugins

Lightweight WooCommerce plugins built for speed. No bloat, no frameworks -- just clean code that works.

View all plugins
Stay in the loop

Get notified when we launch new plugins. No spam, just product updates.