Salesforce
Salesforce Sales Cloud (Lightning Experience) — Enterprise Edition o superior. Professional Edition NO sirve para la integración completa: le falta Salesforce Sites, que es lo único que permite recibir el webhook.
Por qué el calendario es mucho más largo que las horas: entre medio hay esperas que no dependen de nadie del proyecto. Aprovisionar la licencia de integración si no hay ninguna libre puede tardar días de ida y vuelta con el ejecutivo de cuenta de Salesforce. El sandbox tarda en crearse o en refrescarse. Y el paso a producción normalmente espera a una ventana de despliegue del comercio, que suele ser fin de mes o fin de semana. Tres cosas que hacen explotar la estimación, en orden de frecuencia: 1. Descubrir en la semana dos que el comercio está en Professional Edition y que no hay Salesforce Sites. Se rehace toda la arquitectura del webhook. Verificá la edición el primer día. 2. Descubrir que no hay objeto de factura porque el comercio tiene solo Sales Cloud, y que nadie había decidido dónde registrar el cobro. Se frena todo hasta que el contador defina. Por eso está en los prerrequisitos y no en el medio de los pasos. 3. Un consultor que arranca siguiendo un tutorial de Connected App, se estrella contra la restricción de Spring '26, y pierde días averiguando qué pasó. Mostrale el Paso 0 antes de que empiece. Y la aclaración incómoda: nada de esto incluye el costo de las licencias de Salesforce, ni de Revenue Cloud Billing si terminan necesitando el objeto Invoice. Esas conversaciones son con el ejecutivo de cuenta de Salesforce y pueden mover el número final del proyecto mucho más que las horas de programación.
Quien lo hace
Hace falta el socio implementador, sí o sí. No hay vuelta.
Esto no es como conectar una app de contabilidad chica. Salesforce no trae ninguna forma de llamar a una API externa ni de recibir un webhook sin escribir código Apex (el lenguaje de programación propio de Salesforce). No existe el botón. No existe el plugin gratis. Alguien tiene que programar.
Concretamente:
- El DUEÑO: no toca nada. Decide y firma. Su parte es autorizar el gasto, decidir contra qué objeto se registra el cobro (ver Paso 3) y conseguir que le den las licencias.
- EL CONTADOR: tampoco toca nada de la puesta en marcha. Su participación real y necesaria es en una sola definición: decirle al consultor qué campo de Salesforce equivale a "el número de factura" que él usa para conciliar. Si esa definición sale mal, la conciliación no cierra nunca y no hay código que lo arregle después.
- EL ADMINISTRADOR DE SALESFORCE del comercio (si lo tiene): hace la parte de Setup — usuario, permisos, la External Client App, el Site. Son unas 4 a 8 horas de clicks. Necesita perfil "System Administrator".
- EL CONSULTOR / DESARROLLADOR: escribe el Apex, genera el certificado, arma el despliegue a producción. Es el grueso del trabajo.
Si el comercio no tiene administrador de Salesforce propio, el consultor hace las dos partes y el tiempo del comercio se le suma a él.
Advertencia honesta sobre el gasto: en Salesforce el costo de la integración no está en ROKI, está en las horas de consultoría Salesforce, que se cobran caras. Un comercio chico que factura pocas facturas por mes probablemente gaste más en implementar esto que lo que ahorra. Esta integración se justifica cuando Salesforce ya es el sistema donde vive el negocio y hay volumen.
Que tiene que existir antes de empezar
Donde: "Setup" → en la casilla "Quick Find" escribí "Company Information" → "Company Information". Mirá el campo "Organization Edition".
Salesforce Sites — el único mecanismo nativo para recibir el webhook de ROKI — está disponible solo en "Developer, Enterprise, Performance, and Unlimited Editions" según la guía oficial de implementación. Professional Edition queda afuera. Si el comercio está en Professional, la integración se puede hacer igual pero el webhook hay que resolverlo con un intermediario externo (ver la sección de webhook). Verificá esto ANTES de cotizar, no después.
Donde: "Setup" → "Quick Find" → "Profiles" → abrí el perfil correspondiente → sección "Administrative Permissions" → "API Enabled".
Sin API Enabled no hay integración de ningún tipo. En Enterprise y superiores viene activo. En Professional Edition el acceso a API es un complemento que se paga aparte y muchos comercios no lo tienen contratado sin saberlo.
Donde: "Setup" → "Quick Find" → "My Domain".
Es la base de todas las URLs del org, incluida la URL contra la que se pide el token OAuth y la del Site público. Salesforce ya lo exige en todos los orgs nuevos, pero si el org es viejo conviene confirmarlo antes de empezar.
Donde: "Setup" → "Quick Find" → "Sandboxes" → "New Sandbox".
Todo el desarrollo y la prueba se hace acá con las claves sk_test_ de ROKI. Nadie escribe Apex directo en producción. Ojo: un refresh del sandbox borra la configuración que hiciste adentro — la External Client App y el certificado hay que rehacerlos. Coordiná el calendario de refresh antes de empezar.
Donde: "Setup" → "Quick Find" → "Company Information" → mirá la tabla "User Licenses" y buscá la fila "Salesforce Integration".
Es la licencia barata y restringida que se usa para conectar sistemas. Según la nota de versión de Salesforce, los orgs Enterprise, Unlimited y Performance vienen con cinco de estas licencias sin costo adicional, y Developer Edition con una. NO pude leer esa página directamente para confirmarlo palabra por palabra (la ayuda de Salesforce me devolvió error de carga), así que confirmalo en la tabla de tu propio org o con el ejecutivo de cuenta de Salesforce antes de prometerlo. Si no queda ninguna libre, se compran como complemento.
Donde: Decisión de negocio entre el dueño, el contador y el consultor. Se materializa en "Setup" → "Quick Find" → "Object Manager".
Acá está la trampa más grande de Salesforce y hay que decirla sin adornos: Sales Cloud NO trae objeto de factura. El objeto estándar "Invoice" existe en la plataforma, pero la documentación oficial de Revenue Management dice textualmente en sus "Special Access Rules": "You need the Billing Operations User permission set to access this object" — o sea que pertenece a Revenue Cloud Billing, que es un producto aparte que se paga aparte. Si el comercio tiene solo Sales Cloud, no hay factura donde registrar el pago y hay que elegir otra cosa. Ver el Paso 3 para las opciones.
Donde: "Setup" → "Quick Find" → "Sites" → ahí aparece el paso "Register a Salesforce Sites Domain".
Se registra una sola vez por org y sirve para todos los Sites que crees después. Es lo que da la URL pública HTTPS donde ROKI va a golpear con el webhook. Es un paso irreversible en el sentido de que el subdominio elegido queda fijo — elegí un nombre corporativo prolijo, no una prueba.
Donde: Del lado de ROKI, no de Salesforce. Del lado de Salesforce se guardan en un Custom Metadata Type o en un registro protegido, nunca escritas dentro del código Apex.
Si la clave queda hardcodeada en una clase Apex, cualquiera con permiso de ver código la lee, y además tenés que modificar y volver a desplegar código cada vez que rota. Además es motivo de rechazo automático si alguna vez pensás publicar en AppExchange.
Los pasos
- Paso 0 — Leé esto antes que nada: los tutoriales de "Connected App" que vas a encontrar en Google ya no sirven
Este es el cambio más importante y casi nadie lo tiene incorporado todavía. Durante quince años, la forma de conectar cualquier cosa a Salesforce fue crear una "Connected App". Todo tutorial, todo video de YouTube, todo blog dice eso. Ya no se puede. Salesforce publicó el artículo "New Connected Apps Can No Longer Be Created", que dice textual: "Starting in the Spring '26 release, the ability to create new legacy Connected Apps will be disabled." Spring '26 ya pasó. Estamos en agosto de 2026. Qué significa en la práctica: - Las Connected Apps que ya existían siguen funcionando. Nadie se queda sin servicio. - No podés crear una nueva. El reemplazo se llama "External Client App" (ECA). - Hay una puerta de escape temporal: se le puede pedir a Salesforce Support que te habilite la creación de Connected Apps. Pero la propia documentación avisa: "In future releases, Salesforce Support won't be able to enable the creation of new connected apps." Es una prórroga, no una solución. No armes la integración sobre eso. Si tu consultor te cotiza el trabajo hablando de "crear una Connected App", está trabajando con información vieja. Preguntale directamente si sabe que ahora es External Client App. La respuesta te dice bastante sobre qué tan al día está. Toda esta guía usa External Client App. - Paso 1 — Crear el usuario de integración
El principio es simple: ROKI nunca debe entrar a Salesforce con el usuario del dueño ni con el de un empleado. Se crea un usuario que existe solamente para que los sistemas se hablen entre ellos. Por qué importa de verdad: si mañana echás al empleado cuyo usuario se usó para la integración y desactivás su cuenta, los cobros dejan de registrarse en silencio. Nadie se entera hasta que el contador no cuadra el mes. Un usuario de integración dedicado no se desactiva nunca por accidente porque no es de nadie. Cómo se crea: 1. "Setup" → "Quick Find" → "Users" → "Users" → botón "New User". 2. En "User License" elegí "Salesforce Integration". 3. En "Profile" elegí "Minimum Access - API Only Integrations". Ese es el nombre exacto del perfil que crea Salesforce automáticamente al provisionar la licencia, según la documentación oficial. 4. Ponele un nombre que se entienda solo, del tipo "Integracion ROKI Connect", y una casilla de correo a la que el comercio tenga acceso real (sirve para alertas y avisos de vencimiento). 5. Guardá. Qué es exactamente esta licencia, dicho simple: es una licencia barata y restringida, pensada para que un sistema hable con otro. La documentación es explícita en que "API Only means the user can only access Salesforce via a REST, SOAP, or Bulk API and not through a user interface" — este usuario no puede entrar por pantalla ni aunque quiera, y agrega que "You can't turn off the API-only access granted through this profile". Esa restricción no se puede desactivar, y eso es exactamente lo que la hace segura. Advertencia: el perfil "Minimum Access - API Only Integrations" se llama "acceso mínimo" en serio. De fábrica no puede ver ni tocar nada. Todos los permisos se dan en el paso siguiente, uno por uno. - Paso 2 — Darle al usuario de integración los permisos mínimos, y solo esos
Los permisos NO se dan en el perfil. Se dan en un permission set aparte, para que quede documentado y auditable qué puede hacer exactamente la integración. 1. "Setup" → "Quick Find" → "Permission Sets" → "New". 2. Nombralo "ROKI Connect Integracion". 3. Guardá y entrá a editarlo. Lo que hay que habilitar adentro: - "Object Settings": solo el objeto contra el que se registra el cobro (el que definiste en los prerrequisitos). Permisos "Read" y "Edit". Nada de "Delete". Nada de "View All" ni "Modify All". - "Field Permissions" dentro de ese objeto: solo los campos custom de ROKI que vas a crear en el Paso 3, más el campo de referencia que usa el contador. No le des acceso al resto de los campos. - "Apex Class Access": las clases Apex que se escriban en los Pasos 6 y 7. Sin esto el código no corre bajo este usuario. - "System Permissions": "API Enabled" tiene que estar activo. Lo que NO hay que darle, y acá conviene ser terco: NO le asignes el permission set license "Salesforce API Integration". Suena a que hace falta por el nombre, pero la documentación de Salesforce dice que ese permission set license "entitles the Salesforce Integration user license with the same user and object permissions available in the System Administrator profile" — es decir, le da a tu usuario de integración los mismos permisos que un administrador del sistema. Es la salida fácil cuando algo no funciona y el consultor está apurado, y es exactamente la que no hay que tomar. Si algo falla por permisos, se agrega el permiso puntual que falta al permission set, no se abre todo. 4. Volvé al usuario ("Users") → "Permission Set Assignments" → "Edit Assignments" → asigná "ROKI Connect Integracion". - Paso 3 — Definir dónde se registra el cobro (la decisión que el contador tiene que tomar)
Acá hay que ser franco porque es la parte que más proyectos de Salesforce descarrila. Sales Cloud no tiene facturas. Trae "Account", "Contact", "Opportunity", "Quote", "Order", "Product". No trae factura, no trae recibo, no trae pago. Todo lo que suene a facturación en Salesforce se vende aparte. Tres escenarios reales, elegí el que corresponda: ESCENARIO A — El comercio tiene solo Sales Cloud (el caso más común, y de lejos) No hay objeto de factura. Las opciones: - Registrar contra "Opportunity". Se crean campos custom en la Opportunity y cuando entra el pago se marca. Es la opción barata y la que funciona hoy mismo. Su límite es conceptual: una Opportunity es una venta en curso, no un documento fiscal, y si el comercio cobra en varias cuotas el modelo se estira feo. - Crear un objeto custom, por ejemplo "Cobro ROKI__c", con lookup a "Account" y a "Opportunity". Es más trabajo inicial y es lo correcto si hay volumen o pagos parciales. Cada cobro es un registro propio y la historia queda limpia. Decidilo con el contador antes de programar. Migrar de Opportunity a objeto custom después de tener seis meses de datos cargados es un proyecto entero, no un ajuste. ESCENARIO B — El comercio tiene Revenue Cloud Billing Ahí sí existen los objetos "Invoice" y los objetos de Payments. Es el lugar natural para registrar el cobro. Pero ojo con el acceso: la documentación oficial del objeto Invoice dice en "Special Access Rules" que "You need the Billing Operations User permission set to access this object". O sea que al usuario de integración del Paso 1 hay que asignarle también ese permission set, y eso puede consumir una licencia. Confirmalo con el ejecutivo de cuenta antes de diseñar sobre esta base. ESCENARIO C — El comercio factura en otro sistema y Salesforce es solo el CRM Es muy frecuente y nadie lo dice en voz alta. Si la factura fiscal la emite otro sistema, poner el cobro en Salesforce es informativo, no contable. Sigue siendo útil — el vendedor ve que le pagaron — pero no reemplaza la conciliación. Que quede claro con el contador desde el día uno para que nadie espere de Salesforce un reporte que Salesforce no va a poder dar. Los campos custom que hay que crear, en cualquier escenario "Setup" → "Quick Find" → "Object Manager" → elegí el objeto → "Fields & Relationships" → "New": - "ROKI Payment Id" — tipo "Text(64)", marcado como "Unique" y "External ID". Guarda el id que devuelve ROKI. Marcarlo Unique es lo que evita que un webhook repetido genere dos cobros. - "ROKI Checkout URL" — tipo "URL". El link que se le manda al cliente. - "ROKI Estado" — tipo "Picklist" con los valores "Pendiente", "Aprobado", "Rechazado". - "ROKI Monto" — tipo "Currency", con dos decimales. - "ROKI Fecha Acreditacion" — tipo "Date/Time". Y el campo de referencia: el external_reference que se le manda a ROKI tiene que ser el número que el contador usa para conciliar. Si él concilia por número de factura, mandá el número de factura. No mandes el Id interno de Salesforce (esos códigos de 18 caracteres tipo 006Bd00000XXXXX) porque el contador no los reconoce, no los tiene en su plan de cuentas y no le sirven para nada. - Paso 4 — Generar el certificado y crear la External Client App
Esta es la credencial del lado de Salesforce. Reemplaza a la vieja Connected App. PRIMERO el certificado (lo hace el desarrollador, en su máquina, una sola vez) El flujo JWT que vamos a usar no usa contraseña: usa un certificado. La documentación de Salesforce es explícita: "Salesforce requires that a JWT is signed using RSA SHA256, which uses an uploaded certificate as the signing secret". Se genera un par de claves X.509 con openssl. La clave privada queda guardada en el servidor de ROKI y no sale de ahí nunca. El certificado público se sube a Salesforce. Dato práctico que cuesta caro descubrir tarde: el certificado tiene fecha de vencimiento. El día que vence, la integración deja de funcionar de golpe y sin aviso previo. Anotá la fecha en el calendario del comercio el mismo día que lo generás, con recordatorio a treinta días. No es paranoia: es la causa número uno de integraciones Salesforce que "funcionaban perfecto y un día dejaron". DESPUÉS la External Client App 1. "Setup" → "Quick Find" → escribí "App" → seleccioná "App Manager". 2. Botón "New External Client App". Ojo con esto: el botón está en "App Manager", no en "External Client App Manager". "External Client App Manager" es un nodo distinto de Setup que sirve para administrar las apps ya creadas, no para crearlas. Es una inconsistencia de la interfaz de Salesforce que hace perder tiempo a todo el mundo la primera vez. 3. En la sección "Basic Information": - Nombre de la app: "ROKI Connect". - "API name": dejá el que sugiere solo (reemplaza espacios por guiones bajos). - "Contact email": una casilla del comercio que alguien lea de verdad. - "Distribution state": elegí "Local". "Local" es para uso interno de este org, que es tu caso. "Packaged" es para distribuir la app a otros orgs, y eso solo aplica si vas por el camino de AppExchange. 4. En la sección de OAuth marcá "Enable OAuth". 5. "Callback URL": el flujo JWT no la usa realmente, pero el formulario la exige. Poné la URL del endpoint de ROKI o un valor placeholder consistente. 6. Scopes de OAuth. En el selector agregá: - "Manage user data via APIs (api)" - "Perform requests at any time (refresh_token, offline_access)" No agregues más de los que hacen falta. Cada scope de más es superficie de ataque de más, y si algún día vas a AppExchange te lo van a preguntar. 7. Guardá con "Save". Nota sobre los scopes en JWT, para que no te sorprenda: la documentación de Salesforce dice que "You can't specify scopes in a JWT bearer token flow; scopes are issued according to the connected app's Permitted Users policy or your organization's API Access Control settings". El acceso real que va a tener la integración sale del permission set del Paso 2 y de la política del Paso 5, no de lo que marques acá. - Paso 5 — Habilitar el flujo JWT y pre-autorizar el usuario
Sin este paso, la app existe pero rechaza todo intento de conexión con un error críptico. Habilitar JWT: 1. "Setup" → "Quick Find" → "External Client App Manager". 2. Buscá "ROKI Connect" y en el menú de acciones elegí "Edit Settings". 3. Andá a la sección "Flow Enablement" y marcá "Enable JWT Bearer Flow". 4. Elegí "Upload Files" y subí el certificado X.509 que generaste en el Paso 4. 5. Guardá. Pre-autorizar (esto es lo que casi todo el mundo se olvida): 6. En la misma app, andá a la pestaña de políticas — "Policies". 7. En "OAuth policies", en el desplegable "Permitted Users", elegí "Admin approved users are pre-authorized". 8. Guardá. 9. Ahora asociá el permission set: en la sección de políticas de la app, agregá el permission set "ROKI Connect Integracion" a la lista de permission sets seleccionados. Por qué esto es obligatorio y no opcional: el flujo JWT no abre ninguna pantalla de login donde alguien haga click en "Permitir". Nadie autoriza nada en el momento. Salesforce solo emite el token si la autorización ya estaba dada de antemano, y "Admin approved users are pre-authorized" es exactamente eso. La documentación lo confirma: "If your connected app policy is set to 'Admin approved users are pre-authorized,' you can use profiles and permission sets for prior approval when using the JWT bearer flow." Si este paso queda a medias, el error que vas a ver es "Failed: Not Approved for Access" o un "invalid_grant" a secas. El mensaje no dice nada útil. Si aparece, volvé acá primero antes de revisar el código. - Paso 6 — Probar que el token sale (antes de escribir nada más)
Prueba de control. Cinco minutos que ahorran un día entero de debug a ciegas. El desarrollador arma un JWT firmado con la clave privada, con estos claims, según la documentación de claims de Salesforce: - "iss" (Issuer): el Consumer Key de la External Client App. - "sub" (Subject): el username del usuario de integración del Paso 1. Es el username completo, con el sufijo de sandbox incluido si estás en sandbox — se olvida siempre. - "aud" (Audience): "Recipient for whom the token is intended". Para producción es el endpoint de login de Salesforce; para sandbox es el de test. Aclaración honesta: la página de ayuda de Salesforce que detalla el valor exacto de aud para producción contra sandbox me devolvió error de carga y no la pude leer textual, así que confirmá el valor exacto en la documentación oficial antes de codificarlo. No lo adivines. - "exp" (Expiration): "Time after which the token expires". Corto, del orden de un par de minutos. Se firma con RSA SHA256 y se hace POST del JWT al endpoint de token de Salesforce, que es "/services/oauth2/token" sobre el dominio de login correspondiente. Si vuelve un access_token: la mitad difícil ya está resuelta. Si vuelve "invalid_grant": el problema está en el Paso 5 en el 90% de los casos, o el username del claim sub está mal escrito. Regla de seguridad que Salesforce recalca y conviene respetar: "always pass sensitive information in the body of a POST request or in a request header — don't use GET parameters in the URL query string". Nada de tokens ni claves en la URL, porque quedan escritos en los logs de todos los servidores del camino. - Paso 7 — Programar la creación del pago (Apex saliente)
Trabajo del desarrollador. Lo que el dueño necesita entender es qué va a pasar en pantalla. El comportamiento visible: en la Opportunity (o en el objeto que hayan elegido) aparece un botón, digamos "Cobrar con ROKI". El vendedor lo aprieta y a los pocos segundos el campo "ROKI Checkout URL" se llena con un link. Ese link se le manda al cliente por correo o por WhatsApp. Lo que hace por debajo: 1. Pide el token con el flujo del Paso 6 y lo cachea — no se pide un token nuevo por cada llamada. 2. Hace POST a https://aura.roki.systems/api/connect/v1/payments con la cabecera Authorization: Bearer sk_live_... (o sk_test_ en sandbox). 3. Manda el cuerpo: { "amount": 1500.00, "external_reference": "FAC-001", "name": "Factura 001" } 4. Recibe el 201 con { id, checkout_url, transaction_id: null } y guarda el id en "ROKI Payment Id", el checkout_url en "ROKI Checkout URL", y deja "ROKI Estado" en "Pendiente". Dos cosas que hay que dejar bien clavadas en el código: EL MONTO VA EN LEMPIRAS, CON DECIMALES. Mil quinientos lempiras se manda como 1500.00. No como 150000. ROKI no usa centavos. Es el error que más caro sale porque no revienta: el pago se crea igual, el cliente ve un monto cien veces mayor o menor, y el problema se descubre cuando alguien reclama. Si el org tiene multi-moneda activada, ojo doble: hay que mandar el monto en lempiras convertido, no el valor en la moneda del registro. EL PASO PREVIO OBLIGATORIO: la URL de ROKI hay que darla de alta en "Setup" → "Quick Find" → "Remote Site Settings" → "New Remote Site", o configurarla como "Named Credential". Salesforce bloquea toda llamada saliente a un dominio que no esté en esa lista blanca. Sin esto el código tira excepción de callout no autorizado y no hay forma de que funcione. - Paso 8 — Programar el receptor del webhook (Apex REST entrante)
Trabajo del desarrollador. Es la clase que ROKI va a llamar cuando el pago se acredite. Se escribe una clase Apex anotada con "@RestResource" y un método anotado con "@HttpPost". Eso la publica bajo la ruta "/services/apexrest/" seguida del nombre que le hayas dado. Lo que el método tiene que hacer, en orden estricto: 1. Leer el cuerpo CRUDO de la petición, tal cual llegó. En Apex se accede con la propiedad "requestBody", que la referencia oficial define como tipo "Blob" y "Returns or sets the body of the request". Es fundamental usar el cuerpo crudo, sin parsear ni re-serializar el JSON: si lo convertís a objeto y lo volvés a texto, cambia un espacio o el orden de una clave y la firma no valida nunca más. 2. Leer la cabecera "ROKI-Signature" desde la propiedad "headers", que es un "Map<String, String>". Viene con el formato t=<unix>,v1=<hmac_sha256_hex>. 3. Recalcular el HMAC sobre timestamp + "." + cuerpo crudo. En Apex se hace con "Crypto.generateMac" usando el algoritmo HMACSHA256, y el resultado se pasa a hexadecimal con "EncodingUtil.convertToHex" para poder compararlo con el v1 que llegó. 4. Comparar las dos firmas con "areEqualConstantTime", que la documentación describe como el método que "compares two Blobs in constant time in order to avoid timing attack effects". No compares con un == común: es una vulnerabilidad conocida y si alguna vez vas a AppExchange te la marcan. 5. Rechazar el timestamp viejo. Si el t= tiene más de unos minutos, devolvé error. Esto es lo que impide que alguien capture un webhook legítimo y lo reenvíe cien veces. 6. Recién ahora, si la firma valida y el timestamp es fresco, buscar el registro por "ROKI Payment Id" y si el evento es payment.approved poner "ROKI Estado" en "Aprobado" y llenar "ROKI Fecha Acreditacion". 7. Ser idempotente: si el registro ya está en "Aprobado", devolver 200 y no hacer nada más. ROKI puede reenviar el mismo evento. Sin esta comprobación se duplican cobros. 8. Devolver 200 rápido. Toda la lógica pesada — mandar el correo de confirmación, disparar automatizaciones — va en un método asíncrono aparte, no adentro del webhook. Detalle importante sobre el usuario que ejecuta esto: el webhook entra por el Site del Paso 9 y corre bajo el usuario invitado (guest user), no bajo el usuario de integración. El guest user de Salesforce tiene acceso deliberadamente muy restringido a los registros y en general no puede modificar registros que no le pertenecen. Esto sorprende a desarrolladores con experiencia. La clase Apex probablemente tenga que declararse "without sharing" para poder actualizar el registro, y esa decisión hay que tomarla a conciencia, con la lógica de validación bien cerrada, porque estás corriendo código privilegiado disparado desde internet. - Paso 9 — Publicar el endpoint en un Salesforce Site
Este es el paso que convierte la clase Apex en una URL pública a la que ROKI puede llegar. 1. "Setup" → "Quick Find" → "Sites" → "Sites". 2. Si todavía no lo hiciste, registrá el dominio en "Register a Salesforce Sites Domain". Es una sola vez por org y el subdominio queda fijo. 3. Creá un Site nuevo con el botón "New". Nombralo de forma clara, tipo "ROKI Webhook". 4. Guardá. 5. Desde la página de detalle del Site, entrá a "Public Access Settings". Eso abre el perfil del usuario invitado. 6. Ahí habilitá el acceso a la clase Apex del Paso 8. La documentación oficial lo describe como "Enable Apex controllers and methods for your site" y en las buenas prácticas del guest user Salesforce recomienda expresamente "Allow Apex class access only for REST or SOAP API use" — que es exactamente este caso. 7. Dale al guest user permiso de lectura y edición sobre el objeto del cobro y sobre los campos custom de ROKI. Solo esos. Nada más. 8. Volvé al detalle del Site y hacé click en "Activate". La URL final tiene la forma: https://<dominio-del-site>/services/apexrest/<nombre-del-recurso> Esa es la URL que se carga del lado de ROKI como destino del webhook. Fijate que NO es la URL normal del org (la de My Domain, la que usan los empleados). Es la del Site, y es distinta. Confundirlas hace que ROKI reciba 401 en cada intento y el error no dice por qué. Probá con curl desde afuera de la red del comercio antes de dar el paso por terminado. Si el comercio está en Professional Edition: nada de este paso está disponible. La guía de implementación de Salesforce Sites dice textual "Available in: Developer, Enterprise, Performance, and Unlimited Editions". Ver la sección de webhook para la alternativa. - Paso 10 — El respaldo por consulta, que no es opcional
Los webhooks se pierden. Es un hecho de la vida, no una falla de nadie: el Site estaba en mantenimiento, el org tocó un límite, hubo un corte de red de treinta segundos justo ahí. Si el único mecanismo de acreditación es el webhook, tarde o temprano un cobro real queda marcado como "Pendiente" y el cliente ya pagó. Ese es el reclamo que arruina la confianza en el sistema entero. La solución es un job programado: 1. Se escribe una clase Apex que implemente "Schedulable". 2. Corre cada quince o treinta minutos. 3. Busca todos los registros con "ROKI Estado" = "Pendiente" y "ROKI Payment Id" no vacío, creados en los últimos días. 4. Para cada uno consulta GET /payments/{id} contra ROKI. 5. Si ROKI dice aprobado y Salesforce dice pendiente, corrige Salesforce. Se programa desde "Setup" → "Quick Find" → "Apex Classes" → botón "Schedule Apex". Ojo con los límites: la documentación de Apex REST advierte que "Calls to Apex REST classes count against the organization's API governor limits", y las llamadas salientes tienen su propio tope diario. Si el comercio maneja mucho volumen, el job tiene que ir en lotes (Batch Apex) y no en un loop suelto, o vas a agotar el límite de callouts del org y romper otras integraciones que no tienen nada que ver. - Paso 11 — Pasar a producción
1. Probá el circuito completo en sandbox con claves sk_test_: crear el pago, abrir el checkout_url, pagar de prueba, ver que el webhook llegue y que el registro pase a "Aprobado" solo. 2. Probá el camino desagradable: pago rechazado, webhook con firma inválida (tiene que rechazarlo), webhook duplicado (tiene que ser idempotente), y el job de respaldo corrigiendo un pago que quedó pendiente a propósito. 3. Desplegá el código a producción con Change Set o con un package. No se copia y pega Apex en producción. 4. En producción repetí de cero la configuración de Setup: la External Client App, el certificado, el usuario de integración, el permission set, el Site. Nada de eso viaja en un Change Set. Presupuestá estas horas, se olvidan siempre. 5. Cambiá las claves de sk_test_ a sk_live_ y el secreto de firma del webhook. 6. Cargá en ROKI la URL del Site de PRODUCCIÓN. Es distinta de la del sandbox. 7. Primer cobro real: uno chico, de un lempira, con el consultor mirando los logs en vivo. 8. Anotá en el calendario del comercio la fecha de vencimiento del certificado, con alerta a treinta días. - Paso 12 — Sobre AppExchange: leelo antes de que alguien te lo venda
Pregunta directa, respuesta directa: para conectar ROKI a UN comercio, no hace falta AppExchange. Nada. Cero. AppExchange es la tienda de aplicaciones de Salesforce, y sirve para distribuir una app a muchos clientes. Si sos un comercio integrando tu propio Salesforce, instalás tu código en tu org y listo. Si alguien te dice que "hay que publicar en AppExchange" para que esto funcione, o no entendió el problema o te está vendiendo horas. Ahora, si el que lee esto es ROKI o un partner que quiere distribuir la integración empaquetada a muchos comercios, entonces sí, y conviene saber en qué se mete: Qué exige: - Estar inscripto en el ISV Partner Program de Salesforce. Es un contrato comercial con Salesforce, no un formulario. La documentación lo pone como requisito previo: "Enroll your solution in the ISV Partner Program". - Que la solución sea "Lightning Ready". Es obligatorio para todas las presentaciones nuevas. - Un org Developer Edition con la solución instalada y credenciales de prueba funcionando, para que el equipo de seguridad de Salesforce entre y la revise. - Documentación de uso y documentación del flujo de datos entre el org de Salesforce y todo lo que esté afuera. - Reportes de escaneo automático. La documentación es taxativa: "if you're listing a managed package, you're required to upload your Salesforce Code Analyzer scan reports", y hay que justificar por escrito cada falso positivo. Cuánto cuesta: - Novecientos noventa y nueve dólares por cada intento de revisión de seguridad, para soluciones pagas. Y "for any subsequent attempts" — cada reintento se paga de nuevo. Si te rechazan dos veces, pagaste tres veces. - Para soluciones gratuitas la documentación dice que por ahora no hay cargo, "while we work to redefine the policy". Es una política declarada como transitoria, o sea que puede cambiar. - Aparte están los costos del programa de partner en sí, que se negocian con Salesforce y no son públicos. Cuánto tarda de verdad: La documentación oficial desglosa el proceso en etapas: "Initial verification: 1–2 weeks", "First review: 3–4 weeks", "Resubmission review: 2–3 weeks". Y en otra página resume que "A solution typically takes 4–5 weeks to get through the review process". Traducido a la realidad: si pasás a la primera, entre cinco y seis semanas. Casi nadie pasa a la primera. Contá dos o tres meses desde que presentás hasta que estás publicado, y eso sin contar el tiempo de arreglar lo que te marquen, que puede ser bastante si la app tiene código con acceso privilegiado — que es justo el caso de un receptor de webhook público con Apex "without sharing". Y después no termina: "Typically, AppExchange solutions are reviewed for security once a year". Es un compromiso recurrente, no un trámite de una vez. La buena noticia es que "You can release new package versions without re-submitting them for security review" — podés sacar versiones nuevas sin volver a la cola cada vez. Resumen sin vueltas: AppExchange es un canal de distribución con costo real y calendario largo. Vale la pena si vas a vender la integración a decenas de comercios. Para conectar el Salesforce de un comercio, es irrelevante.
Como llega el webhook
SÍ PUEDE, y esta es la buena noticia de Salesforce frente a los ERP instalados en la oficina.
Salesforce vive en la nube. No hay servidor en el depósito, no hay que abrir puertos en el router del local, no hay que pedirle nada al proveedor de internet ni contratar IP fija. La URL ya está en internet y ya tiene HTTPS con certificado válido. Todo el problema clásico de "mi sistema está adentro de la oficina y ROKI no lo puede alcanzar" acá directamente no existe.
CÓMO SE HACE (el camino nativo)
Se combinan dos piezas de Salesforce:
- Una clase Apex con "@RestResource" y "@HttpPost", que queda publicada bajo la ruta "/services/apexrest/".
- Un Salesforce Site, que expone esa clase a internet sin exigir login, corriendo bajo el usuario invitado (guest user).
La URL que se le carga a ROKI queda así:
https://
Configuración: "Setup" → "Quick Find" → "Sites" → crear el Site → "Public Access Settings" → habilitar la clase Apex → volver al Site → "Activate".
LAS TRES ADVERTENCIAS QUE HAY QUE ESCUCHAR
Primera, y es eliminatoria: Salesforce Sites NO existe en Professional Edition. La guía oficial de implementación dice "Available in: Developer, Enterprise, Performance, and Unlimited Editions". Si el comercio está en Professional, este camino está cerrado. Verificalo antes de cotizar.
Segunda: estás abriendo una puerta pública a tu Salesforce. Un endpoint sin autenticación, alcanzable desde cualquier lugar del planeta, que escribe registros en el CRM del negocio. Se puede hacer bien, pero la validación de firma HMAC del Paso 8 deja de ser una buena práctica y pasa a ser lo único que separa tu base de datos de internet. Si esa validación está floja, cualquiera que descubra la URL puede marcar facturas como pagadas. No es teórico.
Tercera: el guest user de Salesforce es deliberadamente muy limitado y no puede modificar registros que no le pertenecen. Salesforce apretó fuerte esta seguridad después de incidentes reales con sitios públicos mal configurados. La documentación es explícita en el riesgo: "Guest user sharing rules grant access to guest users without login credentials, allowing immediate and unlimited access to all records matching the sharing rule's criteria to anyone". El código va a necesitar correr "without sharing" para poder actualizar el registro del cobro, y esa decisión hay que tomarla despierto, con la validación bien cerrada, no copiando de un foro.
SI EL CAMINO NATIVO NO SE PUEDE (Professional Edition, o el área de seguridad del comercio prohíbe sitios públicos)
Alternativa: un relay intermedio. Un servicio chico afuera de Salesforce — puede ser una función serverless de veinte líneas — que hace tres cosas:
- Recibe el webhook de ROKI y valida la firma HMAC ahí, afuera.
- Se autentica contra Salesforce con el mismo flujo JWT del Paso 6, usando el usuario de integración.
- Escribe en Salesforce por la API REST estándar, ya autenticado.
Ventajas reales: Salesforce nunca queda expuesto a internet, no hace falta Salesforce Sites (o sea que Professional Edition sirve), y toda la escritura pasa por el usuario de integración con sus permisos mínimos en vez del guest user. Muchos equipos de seguridad corporativa van a preferir esta opción aunque el comercio tenga Enterprise.
Costo real de la alternativa: una pieza más de infraestructura para mantener, monitorear y pagar. Es más barata de lo que suena — hablamos de centavos de dólar por mes en volumen de comercio — pero alguien tiene que ser responsable de que esté viva.
SI TODO FALLA: el respaldo por consulta
Aunque el webhook funcione perfecto, el job programado del Paso 10 que consulta GET /payments/{id} cada quince o treinta minutos no es opcional. Un comercio en Professional Edition sin relay puede vivir SOLO con este mecanismo: los pagos se acreditan con hasta media hora de demora en vez de al instante. No es ideal, pero funciona y es honestamente aceptable para la mayoría de los negocios. Nadie factura por segundo.
Lo que se rompe
- LA GRANDE: todo tutorial de Connected App que encuentres en internet ya no aplica. Salesforce publicó que "Starting in the Spring '26 release, the ability to create new legacy Connected Apps will be disabled" y Spring '26 ya pasó. Ahora se crean External Client Apps. Hay una excepción temporal pidiéndosela a Salesforce Support, pero la propia documentación avisa que "In future releases, Salesforce Support won't be able to enable the creation of new connected apps". No construyas sobre esa prórroga.
- SALES CLOUD NO TIENE FACTURAS. El objeto "Invoice" existe en la plataforma pero pertenece a Revenue Cloud Billing, que se paga aparte: su documentación dice en Special Access Rules que "You need the Billing Operations User permission set to access this object". Con Sales Cloud pelado hay que registrar el cobro contra Opportunity o contra un objeto custom. Definilo antes de programar, no después.
- SALESFORCE SITES NO EXISTE EN PROFESSIONAL EDITION. Es "Available in: Developer, Enterprise, Performance, and Unlimited Editions". Si el comercio está en Professional no puede recibir el webhook de forma nativa y necesita un relay externo. Chequealo el primer día, no en la semana dos.
- EL MONTO VA EN LEMPIRAS CON DECIMALES, NUNCA EN CENTAVOS. 1500.00 son mil quinientos lempiras. Este error no revienta: el pago se crea igual y el cliente ve un monto cien veces mayor o menor. Se descubre cuando alguien reclama. Si el org tiene multi-moneda activada, ojo redoblado con la conversión.
- EL CERTIFICADO VENCE Y LA INTEGRACIÓN MUERE SIN AVISO. El flujo JWT se apoya en un certificado X.509 con fecha de vencimiento. El día que vence, deja de emitirse el token y los cobros no se acreditan más, sin alerta previa. Anotá la fecha en el calendario del comercio el mismo día que generás el certificado, con recordatorio a treinta días.
- SI TE OLVIDÁS DE "Admin approved users are pre-authorized", el flujo JWT falla con "invalid_grant" o "Failed: Not Approved for Access", errores que no explican nada. El flujo JWT no abre pantalla de login donde alguien apruebe, así que la autorización tiene que estar dada de antemano en las políticas de la app.
- EL BOTÓN PARA CREAR LA APP ESTÁ EN "App Manager", NO EN "External Client App Manager". "External Client App Manager" administra las apps ya creadas. Es una inconsistencia de la interfaz de Salesforce que hace perder la primera media hora a todo el mundo.
- NO LE ASIGNES EL PERMISSION SET LICENSE "Salesforce API Integration" AL USUARIO DE INTEGRACIÓN salvo que sepas exactamente por qué. La documentación dice que "entitles the Salesforce Integration user license with the same user and object permissions available in the System Administrator profile" — le da permisos de administrador. Es el atajo que toma el consultor apurado cuando algo falla por permisos. Agregá el permiso puntual que falta, no abras todo.
- VALIDÁ LA FIRMA SOBRE EL CUERPO CRUDO, sin parsear el JSON y volver a serializarlo. Si convertís a objeto y de vuelta a texto, cambia un espacio o el orden de una clave y la firma no valida más. En Apex se lee con la propiedad "requestBody", que es de tipo Blob.
- COMPARÁ LAS FIRMAS CON "areEqualConstantTime", NO CON ==. Es el método que Salesforke describe como el que "compares two Blobs in constant time in order to avoid timing attack effects". Un == común es una vulnerabilidad conocida y motivo de rechazo si algún día vas a AppExchange.
- EL GUEST USER CASI NO PUEDE TOCAR REGISTROS. Salesforce restringió fuertemente el usuario invitado después de incidentes reales. La documentación advierte que las reglas de compartición para guest users dan "immediate and unlimited access to all records matching the sharing rule's criteria to anyone". El código va a necesitar "without sharing" y esa decisión hay que tomarla con la validación de firma bien cerrada, porque es código privilegiado disparado desde internet.
- LA URL DEL SITE NO ES LA URL DEL ORG. La que usan los empleados (My Domain) y la del Site público son distintas. Cargar la equivocada en ROKI da 401 en cada intento y el error no dice por qué.
- SIN "Remote Site Settings" O UNA NAMED CREDENTIAL, SALESFORCE BLOQUEA LA LLAMADA SALIENTE A ROKI. Es lista blanca obligatoria: "Setup" → "Quick Find" → "Remote Site Settings" → "New Remote Site". Sin esto el código tira excepción de callout no autorizado.
- UN REFRESH DEL SANDBOX BORRA TODA LA CONFIGURACIÓN. La External Client App, el certificado, el Site: nada de eso sobrevive. Y nada de eso viaja en un Change Set tampoco, así que en producción hay que rehacerlo entero a mano. Presupuestá esas horas, se olvidan siempre.
- LOS LÍMITES DE API SON REALES. La documentación de Apex REST dice que "Calls to Apex REST classes count against the organization's API governor limits". Si el job de respaldo consulta en un loop suelto en vez de en lotes, agotás el límite diario del org y rompés otras integraciones que no tienen nada que ver con ROKI.
- EL WEBHOOK TIENE QUE SER IDEMPOTENTE. ROKI puede reenviar el mismo evento. Si el código no verifica que el registro ya esté en "Aprobado" antes de actuar, se duplican cobros. Marcar el campo "ROKI Payment Id" como "Unique" es la red de seguridad a nivel base de datos.
- APPEXCHANGE NO HACE FALTA PARA INTEGRAR UN COMERCIO. Es para distribuir a muchos clientes. Si alguien te dice que hay que publicar ahí para que esto funcione, o no entendió el problema o te está vendiendo horas. Y si sí vas a publicar: 999 dólares por cada intento de revisión, contando los reintentos, y de dos a tres meses realistas.
- SALESFORCE EXIGE 75% DE COBERTURA DE CÓDIGO PARA DESPLEGAR A PRODUCCIÓN. No es una recomendación, es un bloqueo técnico. Las pruebas unitarias no son un extra que se recorta si el presupuesto aprieta: sin ellas el despliegue directamente no pasa.
- EL COSTO REAL DE ESTA INTEGRACIÓN NO ESTÁ EN ROKI, ESTÁ EN LAS HORAS DE CONSULTORÍA SALESFORCE. Un comercio chico con pocas facturas por mes probablemente gaste más en implementar esto que lo que le ahorra. Se justifica cuando Salesforce ya es el sistema donde vive el negocio y hay volumen que lo pague.
Lo que no pudimos confirmar
Cada nombre de menu y cada puerto de esta pagina se verifico contra la documentacion oficial de la plataforma. Estos 5 puntos no se pudieron confirmar, y se dejan senalados en vez de presentarlos como ciertos:
- "Setup" → "Quick Find" → "Profiles" → abrí el perfil → sección "Administrative Permissions" → "API Enabled". - nombre-distinto. La sección "Administrative Permissions" pertenece a la interfaz VIEJA de perfiles, no a la que ve un org Lightning moderno. La doc oficial (Salesforce Security Guide / help.salesforce.com, "User Permi
- Object Manager se llega por "Setup" → "Quick Find" → "Object Manager". - nombre-distinto. Object Manager no es un nodo del árbol de Setup que se busque por Quick Find: es una PESTAÑA de primer nivel en la parte superior de Setup, al lado de "Home". La doc oficial dice "To access the Object
- El campo "Organization Edition" en la página Company Information muestra la edición contratada. - no-verificable. No pude confirmar la etiqueta exacta "Organization Edition" en documentación oficial de Salesforce. Las páginas de help.salesforce.com que documentan los campos de Company Information (company_informa
- "You can't turn off the API-only access granted through this profile". - no-verificable. No pude encontrar esta frase verbatim en documentación oficial. Lo que sí está documentado oficialmente, con otra redacción, es: "API Enabled and API Only user permissions are both set to TRUE and not
- Salesforce Sites es "el único mecanismo nativo para recibir el webhook de ROKI". - no-verificable. No existe documentación oficial de Salesforce que declare a Sites como el único mecanismo nativo para exponer un endpoint público. Es una conclusión de arquitectura del autor, no una cita. La doc sí c
Fuentes
- https://help.salesforce.com/s/articleView?id=005228017&language=en_US&type=1 — "New Connected Apps Can No Long
- https://developer.salesforce.com/docs/platform/mobile-sdk/guide/eca-create.html — "Create an External Client A
- https://trailhead.salesforce.com/content/learn/projects/build-integrations-with-external-client-apps/create-an
- https://help.salesforce.com/s/articleView?id=xcloud.configure_oauth_jwt_flow_external_client_apps.htm&language
- https://help.salesforce.com/s/articleView?id=platform.integration_user.htm&language=en_US&type=5 — "Give Integ
- https://developer.salesforce.com/docs/platform/named-credentials/references/named-credentials-reference/jwt-cl
- https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm — "Introduction t
- https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_restrequest.htm — "
- https://developer.salesforce.com/docs/atlas.en-us.salesforce_platform_portal_implementation_guide.meta/salesfo
- https://developer.salesforce.com/docs/atlas.en-us.revenue_lifecycle_management_dev_guide.meta/revenue_lifecycl
- https://developer.salesforce.com/docs/atlas.en-us.packagingGuide.meta/packagingGuide/security_review_how_it_wo
- https://trailhead.salesforce.com/content/learn/modules/isv_security_review/isv_security_review_submit — "Secur
