Integre seu SaaS em minutos
Copie o prompt abaixo, cole na sua IA favorita (Claude, Cursor, Lovable) junto com o prompt do seu projeto, e seu app nasce integrado.
Copie o prompt
Clique no botao abaixo pra copiar o prompt completo de integracao
Cole na sua IA
Junto com o prompt do seu projeto. A IA gera o app ja integrado.
Configure as envs
Copie sua API Key e Webhook Secret do painel e coloque no .env
Prompt de integracao
Este prompt contem tudo que a IA precisa pra implementar: SSO, webhooks, controle de acesso, tracking, shortcuts e mais.
Funciona com Claude Code, Cursor, Lovable, v0, Bolt e qualquer IA que aceita prompts longos
O que o prompt inclui
Suas credenciais
Acesse Configuracoes ou a pagina de settings do seu produto pra copiar sua API Key e Webhook Secret. Cole no .env do seu projeto:
AFINNY_API_KEY=sua_api_key_aqui AFINNY_WEBHOOK_SECRET=seu_webhook_secret_aqui AFINNY_APP_ID=slug-do-seu-produto AFINNY_API_URL=https://api.afinny.com/v1
Ou copie por partes
Se preferir, copie cada secao separadamente:
1. Variaveis de ambiente
# Adicione ao .env do seu projeto
AFINNY_API_KEY= # API key do painel Afinny → Settings → Integracao
AFINNY_WEBHOOK_SECRET= # Webhook secret do painel Afinny → Settings → Integracao
AFINNY_APP_ID= # Slug do seu produto cadastrado na Afinny
AFINNY_API_URL=https://api.afinny.com/v1
# Validacao no startup do app:
const REQUIRED = ['AFINNY_API_KEY', 'AFINNY_WEBHOOK_SECRET', 'AFINNY_APP_ID']
REQUIRED.forEach(k => { if (!process.env[k]) throw new Error(`[Afinny] Missing: ${k}`) })2. Database migration
ALTER TABLE users ADD COLUMN IF NOT EXISTS afinny_id VARCHAR(255) UNIQUE, ADD COLUMN IF NOT EXISTS afinny_plan VARCHAR(100), ADD COLUMN IF NOT EXISTS afinny_status VARCHAR(50) DEFAULT 'inactive', ADD COLUMN IF NOT EXISTS afinny_free BOOLEAN DEFAULT FALSE, ADD COLUMN IF NOT EXISTS afinny_sub_id VARCHAR(255), ADD COLUMN IF NOT EXISTS afinny_period_end TIMESTAMP, ADD COLUMN IF NOT EXISTS afinny_trial_end TIMESTAMP, ADD COLUMN IF NOT EXISTS afinny_inf_id VARCHAR(255), ADD COLUMN IF NOT EXISTS afinny_inf_username VARCHAR(255), ADD COLUMN IF NOT EXISTS afinny_synced_at TIMESTAMP; CREATE INDEX IF NOT EXISTS idx_users_afinny_id ON users(afinny_id); CREATE INDEX IF NOT EXISTS idx_users_afinny_status ON users(afinny_status);
3. API Client (lib/afinny.ts)
const BASE = process.env.AFINNY_API_URL!
const API_KEY = process.env.AFINNY_API_KEY!
export async function validateSSOToken(token: string) {
const res = await fetch(`${BASE}/sso/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': API_KEY },
body: JSON.stringify({ token }),
})
if (!res.ok) return null
const data = await res.json()
return data.valid ? data : null
}
export function verifyWebhookSignature(payload: string, signature: string | null): boolean {
if (!signature) return false
const crypto = require('crypto')
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.AFINNY_WEBHOOK_SECRET!)
.update(payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}
export function hasActiveAccess(status: string | null): boolean {
return ['active', 'trialing', 'influencer_free'].includes(status ?? '')
}
export async function trackEvent(userId: string, event: string, metadata?: Record<string, unknown>) {
fetch(`${BASE}/track`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': API_KEY },
body: JSON.stringify({ user_id: userId, app_id: process.env.AFINNY_APP_ID, event, metadata }),
}).catch(() => {})
}4. SSO Endpoint (/afinny/auth)
// app/afinny/auth/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from 'next/server'
import { validateSSOToken, trackEvent } from '@/lib/afinny'
export async function GET(req: NextRequest) {
const token = req.nextUrl.searchParams.get('token')
if (!token) return NextResponse.redirect(new URL('/login?error=missing_token', req.url))
const payload = await validateSSOToken(token)
if (!payload) return NextResponse.redirect(new URL('/login?error=invalid_token', req.url))
const { user, subscription } = payload
// Upsert user no seu banco
const localUser = await db.user.upsert({
where: { afinny_id: user.id },
update: {
email: user.email, name: user.name,
afinny_plan: subscription.plan,
afinny_status: subscription.status,
afinny_free: subscription.influencer_free,
afinny_sub_id: subscription.id,
afinny_synced_at: new Date(),
},
create: {
afinny_id: user.id, email: user.email, name: user.name,
afinny_plan: subscription.plan,
afinny_status: subscription.status,
afinny_free: subscription.influencer_free,
afinny_sub_id: subscription.id,
afinny_synced_at: new Date(),
},
})
await trackEvent(user.id, 'session_started')
// Crie sessao local com seu sistema de auth
const response = NextResponse.redirect(new URL('/dashboard', req.url))
// response.cookies.set('session', ...) — adapte ao seu auth
return response
}5. Webhook Receiver
// app/api/webhooks/afinny/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { verifyWebhookSignature } from '@/lib/afinny'
export async function POST(req: NextRequest) {
const body = await req.text()
const signature = req.headers.get('x-afinny-signature')
if (!verifyWebhookSignature(body, signature)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
}
const { event, data } = JSON.parse(body)
switch (event) {
case 'subscription.created':
case 'trial.started':
await db.user.upsert({
where: { afinny_id: data.subscription.user_id },
update: { afinny_status: 'active', afinny_plan: data.subscription.plan },
create: { afinny_id: data.subscription.user_id, email: data.subscription.user_email,
afinny_status: 'active', afinny_plan: data.subscription.plan },
})
break
case 'subscription.canceled':
await db.user.update({
where: { afinny_id: data.subscription.user_id },
data: { afinny_status: 'canceled' },
})
break
case 'affiliate.approved':
await db.user.upsert({
where: { afinny_id: data.affiliate.influencer_id },
update: { afinny_status: 'active', afinny_free: true },
create: { afinny_id: data.affiliate.influencer_id,
email: data.affiliate.influencer_email,
afinny_status: 'active', afinny_free: true },
})
break
case 'affiliate.revoked':
await db.user.update({
where: { afinny_id: data.affiliate.influencer_id },
data: { afinny_status: 'canceled', afinny_free: false },
})
break
}
return NextResponse.json({ received: true })
}6. Access Control Middleware
// middleware.ts
import { hasActiveAccess } from '@/lib/afinny'
const PUBLIC = ['/', '/login', '/afinny/auth', '/api/webhooks/afinny']
export async function middleware(req) {
const path = req.nextUrl.pathname
if (PUBLIC.some(p => path === p || path.startsWith(p))) return NextResponse.next()
const session = await getSession(req) // seu sistema de auth
if (!session) return NextResponse.redirect(new URL('/login', req.url))
if (path.startsWith('/dashboard') && !hasActiveAccess(session.afinny_status)) {
return NextResponse.redirect(new URL('/subscription-required', req.url))
}
return NextResponse.next()
}7. Tracking de uso
// No layout do dashboard, fire-and-forget:
import { trackEvent } from '@/lib/afinny'
// Quando usuario abre o app
trackEvent(user.afinny_id, 'app_opened', { plan: user.afinny_plan })
// Quando usa feature importante
trackEvent(user.afinny_id, 'feature_used', { feature: 'create_project' })8. Home Screen Shortcut
// components/AfinnyShortcutBanner.tsx — adicione no layout root
// Detecta quando esta dentro do Afinny WebView e oferece atalho na home
'use client'
import { useEffect } from 'react'
export function AfinnyShortcutBanner() {
useEffect(() => {
const isAfinny = navigator.userAgent.includes('AfinnyApp')
if (!isAfinny) return
if (localStorage.getItem('afinny_shortcut_shown')) return
// Mostra banner oferecendo adicionar atalho
// Ao clicar: window.ReactNativeWebView.postMessage(JSON.stringify({
// type: 'AFINNY_ADD_SHORTCUT',
// payload: { appName: document.title, iconUrl: '/icon.png' }
// }))
}, [])
return null
}9. Feature Gating por plano
// lib/features.ts
const HIERARCHY = { basic: 1, pro: 2, enterprise: 3, influencer_free: 2 }
export function planIncludes(userPlan: string, required: string): boolean {
return (HIERARCHY[userPlan] ?? 0) >= (HIERARCHY[required] ?? 0)
}
// Uso:
if (!planIncludes(user.afinny_plan, 'pro')) {
return NextResponse.json({ error: 'Upgrade necessario' }, { status: 403 })
}10. Health Check
// app/api/afinny/health/route.ts
export async function GET() {
return NextResponse.json({
status: 'ok',
app_id: process.env.AFINNY_APP_ID,
integration: { sso: true, webhook: true, version: '1.0' },
timestamp: new Date().toISOString(),
})
}11. Checklist final
[ ] AFINNY_API_KEY configurada no .env [ ] AFINNY_WEBHOOK_SECRET configurada no .env [ ] Migration rodada (campos afinny_* na tabela users) [ ] GET /afinny/auth funciona (SSO endpoint) [ ] POST /api/webhooks/afinny funciona (webhook receiver) [ ] HMAC valida assinatura corretamente [ ] Middleware bloqueia acesso sem subscription ativa [ ] GET /api/afinny/health retorna status: ok [ ] Tracking reporta app_opened [ ] URLs registradas no painel Afinny (SSO URL + Webhook URL)
Exemplo de prompt pro dev
"Crie um app web de [seu nicho] chamado [NomeDoApp] com: - [feature 1] - [feature 2] - [feature 3] IMPORTANTE: Este app deve ser integrado com a Afinny. Cole aqui o prompt de integracao copiado acima."
A IA le o prompt de integracao e implementa SSO, webhooks e controle de acesso automaticamente.
Duvidas? Fale com a gente em contato@afinny.com