Conecta tu app con la tienda
Vorka cobra la suscripción de tu app (mensual o anual), la renueva sola y te transfiere tu parte de cada cobro. Tu app solo necesita saber si el correo de quien entra está al corriente.
Cómo funciona
- Creas tu producto en el portal como Suscripción (SaaS / app): precio al mes y/o al año, la URL donde se entra a tu app y tu correo de soporte. Lo revisamos y se publica cuando tus pagos de Stripe están listos.
- Quien compra paga en la tienda. Le llega un correo con el botón a tu app y la indicación de entrar con el mismo correo.
- Cuando entra, tu servidor pregunta a la API si ese correo tiene la suscripción activa. Además te avisamos por webhook de cada alta, renovación, pago fallido y cancelación.
Tu llave de API
En tu portal, Integración con tu app › Crear llave. Empieza con vk_ y solo se muestra una vez: guárdala como secreto en tu servidor (nunca en el navegador ni en una app móvil). Si se filtra, Rotar llave la reemplaza al momento. Cada llave solo ve las suscripciones de tus productos.
Se manda en cada solicitud: Authorization: Bearer vk_…. Límite: 120 solicitudes por minuto (respuesta 429 si lo pasas). Para probarla: GET /api/creadores/v1/yo.
API
¿Este correo tiene la suscripción activa?
GET https://vorka.mx/api/creadores/v1/suscripciones?correo=… · opcional &producto=<slug o id> si vendes más de una app.
curl -s "https://vorka.mx/api/creadores/v1/suscripciones?correo=ana@ejemplo.com" \
-H "Authorization: Bearer $VORKA_API_KEY"
{
"id": "sub_1QX…",
"correo": "ana@ejemplo.com",
"activa": true,
"estado": "active",
"plan": "month",
"periodo_fin": "2026-12-01T18:04:11.000Z",
"cancelar_al_final": false,
"monto_centavos": 19900,
"moneda": "MXN",
"producto": { "id": "prod_mi_app_3f2a1c", "slug": "mi-app", "nombre": "Mi App" }
}
Si ese correo no tiene ninguna suscripción a tus productos, responde 200 con:
{ "activa": false, "estado": null, "plan": null, "periodo_fin": null, "correo": "ana@ejemplo.com" }
| Campo | Qué es |
|---|---|
activa | Lo único que necesitas para dar acceso. true si el estado es active, trialing o past_due (Stripe sigue reintentando el cobro unos días). |
estado | El de Stripe: active, past_due, canceled, unpaid, incomplete… |
plan | month o year. |
periodo_fin | Hasta cuándo está pagado (ISO 8601, UTC). |
cancelar_al_final | true si ya canceló: sigue activa hasta periodo_fin y no se renueva. |
Por id de suscripción
GET https://vorka.mx/api/creadores/v1/suscripciones/sub_… · la misma respuesta; 404 si no es de tus productos.
En JavaScript
// Node 18+, Deno, Bun o Cloudflare Workers (fetch nativo). La llave vive en tu servidor, nunca en el navegador.
export async function tieneAcceso(correo) {
const r = await fetch('https://vorka.mx/api/creadores/v1/suscripciones?correo=' + encodeURIComponent(correo), {
headers: { authorization: 'Bearer ' + process.env.VORKA_API_KEY },
});
if (!r.ok) throw new Error('Vorka respondió ' + r.status);
const s = await r.json();
return s.activa; // true en active, trialing y past_due (Stripe sigue reintentando el cobro)
}
Errores: 400 correo inválido · 401 llave inválida · 404 no es tuyo · 429 límite. Te recomendamos guardar la respuesta unos minutos en tu app en vez de preguntar en cada clic.
Avisos (webhooks)
Guarda en tu portal la URL https de tu app. Te mandamos un POST con JSON en cada evento:
| Tipo | Cuándo |
|---|---|
suscripcion.alta | Se pagó el primer cobro: da acceso. |
suscripcion.renovada | Se pagó una renovación: el acceso sigue hasta el nuevo periodo_fin. |
suscripcion.pago_fallido | Falló un cobro. Stripe reintenta unos días (activa sigue en true mientras tanto); avisa a tu cliente si quieres. |
suscripcion.cancelada | La suscripción terminó (canceló y acabó su periodo, o no se pudo cobrar): quita el acceso. |
prueba | El botón Enviar prueba de tu portal, con datos de ejemplo. |
POST https://miapp.com/webhooks/vorka
content-type: application/json
vorka-evento: suscripcion.renovada
vorka-aviso-id: avi_9c1e…
vorka-firma: t=1764612000,v1=5f2c…
{
"id": "avi_9c1e…",
"tipo": "suscripcion.renovada",
"creado": "2026-12-01T18:04:15.000Z",
"datos": {
"suscripcion": { "id": "sub_1QX…", "correo": "ana@ejemplo.com", "activa": true, "estado": "active", "plan": "month",
"periodo_fin": "2027-01-01T18:04:11.000Z", "cancelar_al_final": false, "monto_centavos": 19900, "moneda": "MXN",
"producto": { "id": "prod_mi_app_3f2a1c", "slug": "mi-app", "nombre": "Mi App" } },
"factura": { "id": "in_1QY…", "monto_centavos": 19900, "moneda": "MXN" }
}
}
Responde con cualquier 2xx en menos de 8 segundos. Si no, reintentamos con espera creciente durante unos dos días; después el aviso queda como fallido en tu portal (y la API siempre tiene el estado al día). Un mismo aviso puede llegar más de una vez: usa id para no procesarlo dos veces.
Verificar la firma
Cada aviso trae Vorka-Firma: t=<segundos unix>,v1=<hex>, donde v1 = HMAC-SHA256(secreto, t + "." + cuerpo) sobre el cuerpo exacto que recibiste. Rechaza firmas de más de 5 minutos. El secreto (whsec_…) está en tu portal, en Ver secreto de firma.
Node.js (Express)
// Node 18+ (Express con express.raw para tener el cuerpo EXACTO, sin volver a serializarlo)
import crypto from 'node:crypto';
import express from 'express';
const SECRETO = process.env.VORKA_WEBHOOK_SECRET; // whsec_… (portal › Integración › Ver secreto de firma)
export function firmaValida(cuerpo, cabecera, tolerancia = 300) {
const partes = Object.fromEntries(String(cabecera || '').split(',').map(p => p.split('=')));
const t = Number(partes.t);
if (!t || Math.abs(Date.now() / 1000 - t) > tolerancia) return false; // evita repetir avisos viejos
const esperada = crypto.createHmac('sha256', SECRETO).update(t + '.' + cuerpo).digest('hex');
const v1 = Buffer.from(partes.v1 || '', 'utf8'), e = Buffer.from(esperada, 'utf8');
return v1.length === e.length && crypto.timingSafeEqual(v1, e);
}
const app = express();
app.post('/webhooks/vorka', express.raw({ type: 'application/json' }), (req, res) => {
const cuerpo = req.body.toString('utf8');
if (!firmaValida(cuerpo, req.get('vorka-firma'))) return res.status(401).end();
const aviso = JSON.parse(cuerpo);
// Idempotencia: puede llegar más de una vez. Guarda aviso.id y no lo proceses dos veces.
const s = aviso.datos.suscripcion;
// aviso.tipo: suscripcion.alta | suscripcion.renovada | suscripcion.pago_fallido | suscripcion.cancelada | prueba
// … activa o desactiva el acceso de s.correo según s.activa
res.status(200).end();
});
Cloudflare Workers / Deno (Web Crypto)
// Cloudflare Workers / Deno / navegador moderno (Web Crypto)
async function firmaValida(cuerpo, cabecera, secreto) {
const p = Object.fromEntries(String(cabecera || '').split(',').map(x => x.split('=')));
if (!p.t || Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const llave = await crypto.subtle.importKey('raw', new TextEncoder().encode(secreto), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
const firma = Uint8Array.from((p.v1 || '').match(/../g) || [], h => parseInt(h, 16));
return crypto.subtle.verify('HMAC', llave, firma, new TextEncoder().encode(p.t + '.' + cuerpo));
}
export default {
async fetch(req, env) {
const cuerpo = await req.text();
if (!(await firmaValida(cuerpo, req.headers.get('vorka-firma'), env.VORKA_WEBHOOK_SECRET))) return new Response('firma', { status: 401 });
const aviso = JSON.parse(cuerpo);
// … guarda aviso.id para no procesarlo dos veces y actualiza el acceso de aviso.datos.suscripcion.correo
return new Response('ok');
},
};
Probar con curl
# Probar tu verificación desde la terminal: firma un cuerpo con tu secreto y mándalo a tu propio endpoint
CUERPO='{"id":"avi_prueba","tipo":"prueba","creado":"2026-11-01T00:00:00.000Z","datos":{}}'
T=$(date +%s)
V1=$(printf '%s' "$T.$CUERPO" | openssl dgst -sha256 -hmac "$VORKA_WEBHOOK_SECRET" | sed 's/^.* //')
curl -s -X POST https://miapp.com/webhooks/vorka -H "content-type: application/json" \
-H "vorka-firma: t=$T,v1=$V1" -d "$CUERPO"
Portal de clientes (tarjeta, recibos, cancelar)
El cobro lo hace Vorka con Stripe. Para que tu cliente cambie su tarjeta, vea sus recibos o cancele, pon un botón en tu app que pida una liga al portal de Stripe (dura unos minutos; pídela al momento del clic). volver debe ser una URL https del mismo dominio que la URL de tu app.
curl -s -X POST "https://vorka.mx/api/creadores/v1/portal" \
-H "Authorization: Bearer $VORKA_API_KEY" -H "content-type: application/json" \
-d '{"correo":"ana@ejemplo.com","volver":"https://miapp.com/cuenta"}'
# → { "url": "https://billing.stripe.com/p/session/…" } (redirige a tu cliente ahí)
Tu cliente también puede hacerlo sin tu app en vorka.mx/tienda/suscripcion con el correo de su compra.
El dinero de cada cobro
En cada cobro (el primero y cada renovación) recibes: cobro − comisión de Vorka − comisión de Stripe de ese cobro. En suscripciones la comisión de Vorka es de 20 % como mínimo.
Ejemplo con un plan de $199 al mes pagado con tarjeta nacional, suponiendo una comisión de Stripe de $11.79 en ese cobro (3.6 % + $3 + IVA; la real depende de la tarjeta, ver stripe.com/mx/pricing): Vorka $39.80 · Stripe $11.79 · tú $147.41. En tu portal ves el desglose real de cada cobro.
Se transfiere a tu cuenta de Stripe 7 días en planes mensuales y 11 en anuales después de cada cobro (cubre el plazo de reembolso). Si un cobro se reembolsa completo o el cliente lo desconoce con su banco, esa transferencia se cancela o se revierte.
Las reglas completas están en los términos para creadores. ¿Dudas técnicas? hola@vorka.mx.