Recovery Alert Subscription Guide
Set up email and SMS notifications for recovery requests to keep Safe account owners informed of recovery attempts. This security feature provides an essential early warning system during the recovery process.
The Recovery Service requires access credentials. Request access here.
To set up the recovery module first, see the Enable Recovery Module and Add Guardians guide.
Overview
The Recovery Alert system provides:
- Instant Notifications: Immediate email and SMS alerts when recovery requests are initiated
- On-Chain Monitoring: Tracks on-chain recoveries across all supported networks
- Grace Period Warnings: Alerts during the grace period enabling owners to respond to unauthorized attempts
- Signature Verification: Only verified account owners can subscribe to alerts
To learn more, visit the Safe Recovery Service API section.
Prerequisites
Installation
- npm
- yarn
npm i safe-recovery-service-sdk abstractionkit viem
yarn add safe-recovery-service-sdk abstractionkit viem
safe-recovery-service-sdkprovides the alert subscription functionalityabstractionkitprovides Safe account creation and managementviemis used for message signing and wallet operations
Environment Setup
# Network Configuration
CHAIN_ID=11155111 # Sepolia testnet chain ID
NODE_URL=https://ethereum-sepolia-rpc.publicnode.com
# Bundler and Paymaster URLs
BUNDLER_URL=https://api.candide.dev/public/v3/11155111
PAYMASTER_URL=https://api.candide.dev/public/v3/11155111
# Recovery Service URL
# Get access here: https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5
RECOVERY_SERVICE_URL=
# Safe Owner
OWNER_PRIVATE_KEY=0x..
# Alert Configuration
USER_EMAIL=owner@example.com
USER_PHONE=+1234567890
Alert Subscription Steps
Fork the complete code to follow along.
Step 1: Initialize Alert Service
Set up the recovery service client and account credentials.
import { Alerts } from "safe-recovery-service-sdk";
import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit";
import { privateKeyToAccount } from "viem/accounts";
import * as dotenv from 'dotenv';
dotenv.config();
const recoveryServiceURL = process.env.RECOVERY_SERVICE_URL as string;
const ownerPrivateKey = process.env.OWNER_PRIVATE_KEY as `0x${string}`;
const ownerEmail = process.env.USER_EMAIL as string;
const ownerPhone = process.env.USER_PHONE as string;
const chainId = BigInt(process.env.CHAIN_ID as string);
const ownerAccount = privateKeyToAccount(ownerPrivateKey);
const smartAccount = SafeAccount.initializeNewAccount([ownerAccount.address]);
const safeAccountAddress = smartAccount.accountAddress;
const alertsService = new Alerts(recoveryServiceURL, chainId);
Step 2: Create SIWE Messages
Generate Sign-in with Ethereum (EIP-4361) messages for subscription verification using the SDK helper methods.
- Email Subscription
- SMS Subscription
const emailSiweMessage = alertsService.createEmailSubscriptionSiweStatementToSign(
safeAccountAddress,
ownerAccount.address,
ownerEmail
);
const smsSiweMessage = alertsService.createSubscriptionSiweStatementToSign(
safeAccountAddress,
ownerAccount.address,
"sms",
ownerPhone
);
Step 3: Sign Messages and Create Subscriptions
Sign the SIWE messages and submit subscription requests for both email and SMS.
- Email Subscription
- SMS Subscription
const emailSignature = await ownerAccount.signMessage({
message: emailSiweMessage
});
const emailSubscriptionId = await alertsService.createEmailSubscription(
safeAccountAddress,
ownerAccount.address,
ownerEmail,
emailSiweMessage,
emailSignature
);
const smsSignature = await ownerAccount.signMessage({
message: smsSiweMessage
});
const smsSubscriptionId = await alertsService.createSubscription(
safeAccountAddress,
ownerAccount.address,
"sms",
ownerPhone,
smsSiweMessage,
smsSignature
);
Step 4: Activate Subscriptions
Enter the verification codes received via email and SMS to activate both subscriptions.
- Email Activation
- SMS Activation
const emailVerificationCode = "123456";
await alertsService.activateSubscription(
emailSubscriptionId,
emailVerificationCode
);
const smsVerificationCode = "654321";
await alertsService.activateSubscription(
smsSubscriptionId,
smsVerificationCode
);
Step 5: Verify Active Subscriptions
Check your current active alert subscriptions.
const getSubscriptionsSiweMessage = alertsService.getSubscriptionsSiweStatementToSign(
ownerAccount.address
);
const getSubscriptionsSignature = await ownerAccount.signMessage({
message: getSubscriptionsSiweMessage
});
const activeSubscriptions = await alertsService.getActiveSubscriptions(
safeAccountAddress,
ownerAccount.address,
getSubscriptionsSiweMessage,
getSubscriptionsSignature
);
Complete Working Example
Full Alert Subscription Example
loading...
What's Next
- Enable Email / SMS Recovery: add Candide's managed guardian service so users can recover via email or SMS without managing guardian keys
- Recovery API Reference: explore the full alert and recovery API surface
Managing Subscriptions
Update Contact Information
To change your alert email or phone number:
- Unsubscribe from current alerts for that channel using the unsubscribe method with SIWE authentication
- Subscribe with new contact information using the subscription flow
- Verify the new subscription by entering the OTP code sent to your new contact
Unsubscribe from Specific Channel
To stop receiving alerts on one channel:
// Generate SIWE message for unsubscribing
const unsubscribeSiweMessage = alertsService.unsubscribeSiweStatementToSign(
ownerAccount.address
);
// Sign the SIWE message
const unsubscribeSignature = await ownerAccount.signMessage({
message: unsubscribeSiweMessage
});
// Unsubscribe from email only
const emailUnsubscribeSuccess = await alertsService.unsubscribe(
"your-email-subscription-id",
ownerAccount.address,
unsubscribeSiweMessage,
unsubscribeSignature
);
if (emailUnsubscribeSuccess) {
console.log("Successfully unsubscribed from email alerts");
// SMS subscription remains active
}
Unsubscribe from All Channels
To stop all recovery alerts:
// Generate SIWE message for unsubscribing
const unsubscribeSiweMessage = alertsService.unsubscribeSiweStatementToSign(
ownerAccount.address
);
// Sign the SIWE message
const unsubscribeSignature = await ownerAccount.signMessage({
message: unsubscribeSiweMessage
});
// Unsubscribe from all channels
const emailUnsubscribeSuccess = await alertsService.unsubscribe(
"email-subscription-id",
ownerAccount.address,
unsubscribeSiweMessage,
unsubscribeSignature
);
const smsUnsubscribeSuccess = await alertsService.unsubscribe(
"sms-subscription-id",
ownerAccount.address,
unsubscribeSiweMessage,
unsubscribeSignature
);
if (emailUnsubscribeSuccess && smsUnsubscribeSuccess) {
console.log("Successfully unsubscribed from all alerts");
}