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_issuedis 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);
| Argument | Type | Description |
|---|---|---|
$user_id | int | WordPress user ID of the customer who earned the reward. |
$reward | object | Reward record. Properties: id, title, type (will be manual), meta (JSON string), active. |
$rank | object | Rank record. Properties: id, name, slug, milestone, active. |
$message | string | Internal 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.
- Copy
templates/points-ranks.phpfrom the plugin into your theme atyour-theme/wpgens-loyalty-program/points-ranks.php. - Find the
foreach ($rewards as $reward)loop inside.wpgens-loyalty-rewards-list. - 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.