Automatic level up after successful KYC verification

I’m looking to develop a plugin that automatically updates a user’s tier once their KYC verification is successfully completed and their documents are approved. At the moment, we handle this process manually, but I’d like to streamline it.

Does the plugin system provide a way to trigger these tier updates automatically when KYC verification is confirmed? Any guidance or examples on how to implement this would be greatly appreciated.

Thanks in advance!

3 Likes

In HollaEx, user tiers are represented by verification_level, while KYC status is stored in id_data.status.

id_data.status values are typically:

  • 0: no status
  • 1: pending (KYC is being processed)
  • 2: rejected
  • 3: completed (approved)

There are two approaches to do this using Event-driven and Cron-based setup.

Event-driven (recommended on v2.17.1+)

Plugins can subscribe to the internal events channel:

toolsLib.database.subscriber.subscribe('channel:events');

toolsLib.database.subscriber.on('message', async (channel, message) => {
  try {
    if (channel === 'channel:events') {
      const { type, data } = JSON.parse(message);

      // For tier changes:
      if (type === 'user' && data.action === 'update') {
         loggerPlugin.info('/plugins/tier-upgrade-kyc/events update',
           data.user_id,
           data.email,
           data.id_data);
        // upgrade user's verification_level if all conditions are met

      }
    }
  } catch (err) {
    loggerPlugin.error('/plugins/tier-upgrade-kyc/events', err);
  }
});

From there, you can implement your logic to upgrade the user automatically (e.g., when you confirm id_data.status === 3, or when your KYC provider marks the user approved), using:

await toolsLib.user.changeUserVerificationLevelById(userId, 2);

Cron-based approach

You can periodically scan for users who:

  • are active (activated: true)
  • are not flagged (flagged: false)
  • are currently Tier 1 (verification_level: 1)
  • have approved KYC (id_data.status: 3)

Example query:

const users = await toolsLib.database.findAll('user', {
  where: {
    activated: true,
    flagged: false,
    verification_level: 1,
    id_data: {
      status: 3
    }
  },
  raw: true,
  attributes: [
    'id',
    'email',
    'network_id',
    'id_data',
    'verification_level',
    'email_verified',
    'activated',
    'flagged',
    'created_at'
  ]
});

Then loop through the matches and upgrade them:

await toolsLib.user.changeUserVerificationLevelById(user.id, 2);

Full example plugin (runs hourly for cron with hook-based setup)

'use strict';

const {
	toolsLib,
	loggerPlugin,
	cron
} = this;

const EVENTS_CHANNEL = 'channel:events';

// Event-based approach
toolsLib.database.subscriber.subscribe(EVENTS_CHANNEL);

toolsLib.database.subscriber.on('message', async (channel, message) => {
	try {
		if (channel !== EVENTS_CHANNEL) return;

		const { type, data } = JSON.parse(message);

		// For tier changes:
		if (type === 'user' && data && data.action === 'update') {
			loggerPlugin.info(
				'/plugins/tier-upgrade-kyc/events update',
				data.user_id,
				data.email,
				data.id_data,
				data.previous_user
			);

			// Extra guard: only upgrade if the user is still eligible (prevents loops / bad events)
			const user = await toolsLib.database.findOne('user', {
				where: { id: data.user_id },
				raw: true,
				attributes: ['id', 'email', 'id_data', 'verification_level', 'activated', 'flagged']
			});

			if (
				user &&
				user.activated === true &&
				user.flagged === false &&
				user.verification_level === 1 &&
				user.id_data &&
				user.id_data.status === 3
			) {
				loggerPlugin.verbose(
					'/plugins/tier-upgrade-kyc/events',
					`Upgrading user ${user.id} (${user.email}) -> level 2`
				);
				await toolsLib.user.changeUserVerificationLevelById(user.id, 2);
			}
		}
	} catch (err) {
		loggerPlugin.error('/plugins/tier-upgrade-kyc/events', err);
	}
});

// Cron-based approach
const run = async () => {
	loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', 'Running tier upgrade KYC plugin');

	const users = await toolsLib.database.findAll('user', {
		where: {
			activated: true,
			flagged: false,
			verification_level: 1,
			id_data: {
				status: 3
			}
		},
		raw: true,
		attributes: [
			'id',
			'email',
			'network_id',
			'id_data',
			'verification_level',
			'email_verified',
			'activated',
			'flagged',
			'created_at'
		]
	});

	loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', `Found ${users.length} users to upgrade`);

	if (!users.length) return;

	for (const user of users) {
		loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', `Upgrading user ${user.id} (${user.email}) -> level 2`);
		await toolsLib.user.changeUserVerificationLevelById(user.id, 2);
		loggerPlugin.verbose('/plugins/tier-upgrade-kyc/run', `User ${user.id} upgraded successfully`);
	}
};


run();

// Runs at minute 0 of every hour (UTC)
const task = cron.schedule(
	'0 * * * *',
	() => run(),
	{ timezone: 'Etc/UTC' }
);

task.start();
1 Like

This is very interesting with the redis subscriber for the hook based. What are all the events other than this that you can subscribe too?

2 Likes