From persona-pack
Implements webhook handlers for Persona identity verification events with HMAC signature verification, event processing for status changes, and database updates in Node.js/Express.
How this skill is triggered — by the user, by Claude, or both
Slash command
/persona-pack:persona-webhooks-eventsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
HMAC signature verification, inquiry.completed/approved/declined events, idempotent processing.
HMAC signature verification, inquiry.completed/approved/declined events, idempotent processing.
persona-install-auth setup1. Dashboard > Settings > Webhooks > Add Webhook
2. URL: https://your-app.com/webhooks/persona
3. Events: inquiry.completed, inquiry.approved, inquiry.declined,
verification.passed, verification.failed
4. Copy the webhook secret for signature verification
import express from 'express';
import crypto from 'crypto';
const app = express();
app.post('/webhooks/persona',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['persona-signature'] as string;
const secret = process.env.PERSONA_WEBHOOK_SECRET!;
// Verify HMAC-SHA256 signature
const expectedSig = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature || ''), Buffer.from(expectedSig))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
await handlePersonaEvent(event);
res.status(200).json({ received: true });
}
);
async function handlePersonaEvent(event: any) {
const { type, data } = event;
switch (type) {
case 'inquiry.completed':
const inquiryId = data.attributes.payload.data.id;
const referenceId = data.attributes.payload.data.attributes['reference-id'];
console.log(`Inquiry completed: ${inquiryId} for user ${referenceId}`);
// Update user KYC status in your database
await updateUserKycStatus(referenceId, 'completed');
break;
case 'inquiry.approved':
await updateUserKycStatus(data.attributes.payload.data.attributes['reference-id'], 'approved');
break;
case 'inquiry.declined':
await updateUserKycStatus(data.attributes.payload.data.attributes['reference-id'], 'declined');
break;
case 'verification.passed':
console.log(`Verification passed: ${data.attributes.payload.data.id}`);
break;
case 'verification.failed':
console.log(`Verification failed: ${data.attributes.payload.data.id}`);
break;
default:
console.log(`Unhandled event: ${type}`);
}
}
const processedEvents = new Set<string>();
async function idempotentHandle(event: any) {
const eventId = event.data.id;
if (processedEvents.has(eventId)) {
console.log(`Skipping duplicate: ${eventId}`);
return;
}
await handlePersonaEvent(event);
processedEvents.add(eventId);
}
| Issue | Cause | Solution |
|---|---|---|
| Invalid signature | Wrong webhook secret | Re-copy secret from Dashboard |
| Missing events | Events not selected | Check webhook configuration |
| Duplicate processing | Retry delivery | Use event ID deduplication |
For common errors, see persona-common-errors.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin persona-packSecures Persona API keys via rotation and env vars, verifies webhook HMAC, encrypts PII at rest, and sets up audit logging for identity verification.
Handles Clerk webhooks for real-time events like user, session, and organization changes. Includes verification, routing, and integration patterns.
Sets up Clerk webhook endpoints in Next.js to verify signatures and handle auth events for user sync using @clerk/backend or Svix.