Best Practices
Guidelines for building secure, performant, and maintainable integrations with Yeld.
Security Best Practices
Protect Your Master Mnemonic
Your master mnemonic is the key to all your business wallets. Store it securely and never expose it to the client.
// ✅ Good: Store in environment variable
EVM_MASTER_MNEMONIC=your_secure_mnemonic
// ❌ Bad: Hardcoding in source
const mnemonic = "your mnemonic here";Use Environment Variables
Store all secrets in environment variables. Never commit .env files to version control.
// .env.local (add to .gitignore)
EVM_MASTER_MNEMONIC=...
SUPABASE_SERVICE_ROLE_KEY=...
// .gitignore
.env.local
.env.productionValidate All Inputs
Always validate user inputs on the server side. Don't trust client-side validation alone.
// Server action with validation
"use server";
export async function createCheckoutLink(bizId: string, input: Input) {
// Validate business ownership
const user = await getCurrentUser();
if (!user) throw new Error("Unauthorized");
const business = await getBusiness(bizId);
if (!business || business.ownerId !== user.id) {
throw new Error("Not authorized");
}
// Validate price
const price = Number(input.price);
if (Number.isNaN(price) || price <= 0) {
throw new Error("Invalid price");
}
// ... rest of logic
}Verify Webhook Signatures
Always verify webhook signatures to ensure requests are authentic.
import { createHmac } from "crypto";
function verifySignature(payload: string, signature: string): boolean {
const expected = createHmac("sha256", process.env.YELD_WEBHOOK_SECRET!)
.update(payload)
.digest("hex");
return signature === expected;
}Performance Optimization
Use Server Components
Leverage Next.js server components to reduce client-side JavaScript bundle size.
// Server component (default in App Router)
export default async function DashboardPage({ params }) {
const business = await getBusiness(params.bizId);
const wallet = await getOrCreateBusinessWallet(business.id);
return <Dashboard business={business} wallet={wallet} />;
}Implement Caching
Use Next.js caching for frequently accessed data that doesn't change often.
import { unstable_cache } from "next/cache";
export const getRealtimePrices = unstable_cache(
fetchRealtimePrices,
["crypto-usd-prices"],
{ revalidate: 30 } // Revalidate every 30 seconds
);Optimize Database Queries
Use proper indexes and avoid N+1 query problems.
// ✅ Good: Single query with joins
const results = await db
.select()
.from(checkoutLinks)
.where(eq(checkoutLinks.businessId, businessId))
.orderBy(desc(checkoutLinks.createdAt));
// ❌ Bad: Multiple queries in loop
for (const link of links) {
const sessions = await db
.select()
.from(checkoutSessions)
.where(eq(checkoutSessions.checkoutLinkId, link.id));
}User Experience Best Practices
Provide Clear Feedback
Show loading states, success messages, and clear error messages to users.
// Show loading and error states
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
<Button onClick={handleAction} disabled={loading}>
{loading ? "Processing..." : "Submit"}
</Button>
{error && (
<p className="text-sm text-destructive">{error}</p>
)}Handle Errors Gracefully
Catch errors and provide helpful error messages. Don't expose internal details to users.
try {
await createCheckoutLink(bizId, input);
} catch (error) {
// Log error internally
console.error("Failed to create link:", error);
// Show user-friendly message
setError("Failed to create checkout link. Please try again.");
}Make Actions Reversible
Allow users to undo actions when possible, or provide clear confirmation dialogs.
// Confirmation dialog for destructive actions
<ConfirmDialog
onConfirm={() => deleteCheckoutLink(bizId, planId)}
confirmationText="This action cannot be undone."
/>Integration Best Practices
Use Type Safety
Leverage TypeScript for type-safe integrations. Define interfaces for your data models.
// Define types for your integration
interface CheckoutLink {
planId: string;
planName: string;
price: number;
billing: "recurring" | "one-time";
createdAt: Date;
}
// Use typed API responses
async function getLinks(bizId: string): Promise<CheckoutLink[]> {
const response = await fetch(...);
return response.json() as Promise<CheckoutLink[]>;
}Implement Idempotency
Make your webhook handlers idempotent to handle duplicate events safely.
// Check if event was already processed
async function handleWebhook(event: WebhookEvent) {
// Check if we've already processed this event
const existing = await db
.select()
.from(processedEvents)
.where(eq(processedEvents.eventId, event.id))
.limit(1);
if (existing.length > 0) {
return; // Already processed
}
// Process event
await processEvent(event);
// Mark as processed
await db.insert(processedEvents).values({ eventId: event.id });
}Test Thoroughly
Test your integration thoroughly before going to production. Use testnets and sandbox environments.
// Test file example
describe("Checkout Links", () => {
it("should create a checkout link", async () => {
const result = await createCheckoutLink("test-biz-id", {
planName: "Test Plan",
price: "10.00",
billing: "one-time",
});
expect(result.ok).toBe(true);
});
it("should reject invalid prices", async () => {
const result = await createCheckoutLink("test-biz-id", {
planName: "Test Plan",
price: "-1.00",
billing: "one-time",
});
expect(result.ok).toBe(false);
});
});Additional Tips
Monitor Your Dashboard
Regularly check your dashboard for unusual activity or errors.
Keep Dependencies Updated
Regularly update your dependencies to get security patches and new features.
Document Your Integration
Keep documentation of your integration for future reference and team members.
Plan for Scaling
Design your integration to handle increased traffic and transactions as your business grows.