Skip to main contentCertyo Developer Portal

Integración con Salesforce

Conecta Salesforce a Certyo usando Platform Events, callouts de Apex y objetos personalizados. Ancla automáticamente datos de cumplimiento de pedidos en la blockchain y rastrea el estado del certificado directamente en Salesforce.

Requisitos previos

  • Edición Salesforce Enterprise o Unlimited (Platform Events y Named Credentials requieren estas ediciones)
  • Una clave API de Certyo — consulta la Guía de autenticación
  • Administrador del sistema o un conjunto de permisos personalizado con acceso a clases Apex, Platform Events y Named Credentials
  • My Domain habilitado (requerido para Named Credentials)

Arquitectura

Integration flowbash
┌──────────────────────────────────┐
│        Salesforce Org             │
│                                   │
│  Order status -> "Fulfilled"      │
│         │                         │
│         v                         │
│  Apex Trigger (OrderTrigger)      │
│         │                         │
│         v                         │
│  Platform Event                   │
│  (Certyo_Record_Ingest__e)        │
│         │                         │
│         v                         │
│  Apex Trigger (Platform Event)    │
│  -> @future(callout=true)         │
│         │                         │
│         v                         │
│  CertyoService.ingestRecord()     │──────────┐
│  (Named Credential callout)       │          │
│         │                         │          │ HTTPS POST
│         v                         │          │ X-API-Key
│  Certificate__c record            │          │
│  (AnchorStatus = Pending)         │          v
│                                   │  ┌──────────────────┐
│  Scheduled Apex (every 15 min)    │  │ Certyo API       │
│  -> CertyoService.verifyRecord()  │  │ POST /api/v1/    │
│  -> Certificate__c updated        │  │    records       │
│     (AnchorStatus = Anchored)     │  │                  │
└──────────────────────────────────┘  │ 202 Accepted     │
                                      └────────┬─────────┘
                                               │
                                               v
                                      ┌──────────────────┐
                                      │  Pipeline         │
                                      │  Kafka ->         │
                                      │  Accumulate ->    │
                                      │  Merkle Tree ->   │
                                      │  IPFS -> Polygon  │
                                      └──────────────────┘

Objeto personalizado: Certificate__c

Crea un objeto personalizado para rastrear el ciclo de vida del certificado blockchain para cada registro. Navega a Setup > Object Manager > Create > Custom Object.

Nombre API del campoTipoDescripción
RecordId__cText(255)El recordId de Certyo (por ejemplo, el número de pedido)
RecordHash__cText(255)Hash SHA-256 retornado por Certyo en la ingesta
SnapshotId__cText(255)ID de snapshot de Certyo que contiene este registro
MerkleRoot__cText(255)Hash raíz del Merkle tree para el lote de anclaje
AnchorStatus__cPicklistPending | Batched | Anchored | Failed
PolygonTxHash__cText(255)Hash de transacción en Polygon una vez anclado on-chain
IpfsCid__cText(255)Identificador de contenido IPFS para el manifiesto
VerifiedAt__cDateTimeMarca de tiempo de la última verificación exitosa
Order__cLookup(Order)Registro de pedido relacionado para navegación y reportes

Named Credential

Almacena el endpoint del API de Certyo y el encabezado de autenticación en una Named Credential para que el código Apex nunca maneje secretos directamente. Navega a Setup > Named Credentials > New Named Credential.

Named Credential configurationbash
Label:              Certyo API
Name:               Certyo_API
URL:                https://www.certyos.com
Identity Type:      Named Principal
Authentication:     Custom Header
Header Name:        X-API-Key
Header Value:       certyo_sk_live_your_key_here
Generate Auth Header: unchecked
Allow Merge Fields:   checked
Límites de callout
Salesforce aplica un límite de 100 callouts por transacción Apex. Usa Platform Events con @future(callout=true) o Queueable Apex para hacer callouts fuera del contexto de transacción del trigger. Nunca hagas callouts directamente dentro del cuerpo de un trigger.

Implementación

Clase de servicio principal que maneja tanto la ingesta de registros como la verificación. Usa la Named Credential para autenticación, así que no aparecen claves API en el código.

CertyoService.clscsharp
/**
 * CertyoService — Apex class for Certyo API integration.
 * Uses Named Credential 'Certyo_API' for authentication.
 *
 * Methods:
 *   ingestRecord()  — POST /api/v1/records (async via @future)
 *   verifyRecord()  — POST /api/v1/verify/record
 *   queryRecord()   — GET  /api/v1/records?tenantId=X&recordId=Y
 */
public with sharing class CertyoService {

    private static final String NAMED_CREDENTIAL = 'callout:Certyo_API';
    private static final String INGEST_PATH      = '/api/v1/records';
    private static final String VERIFY_PATH      = '/api/v1/verify/record';
    private static final String QUERY_PATH       = '/api/v1/records';

    // Tenant ID — configure via Custom Metadata Type or Custom Setting
    private static String getTenantId() {
        Certyo_Settings__c settings = Certyo_Settings__c.getOrgDefaults();
        return settings.Tenant_Id__c != null
            ? settings.Tenant_Id__c
            : 'default';
    }

    /**
     * Ingest a record asynchronously.
     * Called via @future so it runs outside the trigger transaction.
     *
     * @param orderId      The Order record ID (Salesforce ID)
     * @param orderNumber  The Order number (used as Certyo recordId)
     * @param payloadJson  Serialized JSON of the order data
     */
    @future(callout=true)
    public static void ingestRecord(
        Id orderId,
        String orderNumber,
        String payloadJson
    ) {
        String tenantId = getTenantId();
        String today = String.valueOf(Date.today());

        Map<String, Object> body = new Map<String, Object>{
            'tenantId'       => tenantId,
            'database'       => 'salesforce',
            'collection'     => 'orders',
            'recordId'       => orderNumber,
            'recordVersion'  => '1',
            'operationType'  => 'insert',
            'recordPayload'  => (Map<String, Object>) JSON.deserializeUntyped(payloadJson),
            'sourceTimestamp' => Datetime.now().formatGmt('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''),
            'idempotencyKey' => orderNumber + '-v1-' + today
        };

        HttpRequest req = new HttpRequest();
        req.setEndpoint(NAMED_CREDENTIAL + INGEST_PATH);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(body));
        req.setTimeout(30000);

        Http http = new Http();
        HttpResponse res = http.send(req);

        Certificate__c cert = new Certificate__c(
            Order__c       = orderId,
            RecordId__c    = orderNumber,
            AnchorStatus__c = 'Pending'
        );

        if (res.getStatusCode() == 202) {
            Map<String, Object> result =
                (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            cert.RecordHash__c = (String) result.get('recordHash');
            System.debug('Certyo ingestion accepted: ' + orderNumber +
                         ' hash=' + cert.RecordHash__c);
        } else {
            cert.AnchorStatus__c = 'Failed';
            System.debug('Certyo ingestion failed: HTTP ' +
                         res.getStatusCode() + ' ' + res.getBody());
        }

        upsert cert RecordId__c;
    }

    /**
     * Verify a record against the on-chain Merkle root.
     *
     * @param recordId    The Certyo recordId (e.g. Order number)
     * @param payloadJson The original record payload as JSON
     * @return Map with verification result fields
     */
    public static Map<String, Object> verifyRecord(
        String recordId,
        String payloadJson
    ) {
        String tenantId = getTenantId();

        Map<String, Object> body = new Map<String, Object>{
            'tenantId'   => tenantId,
            'database'   => 'salesforce',
            'collection' => 'orders',
            'recordId'   => recordId,
            'payload'    => (Map<String, Object>) JSON.deserializeUntyped(payloadJson)
        };

        HttpRequest req = new HttpRequest();
        req.setEndpoint(NAMED_CREDENTIAL + VERIFY_PATH);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(body));
        req.setTimeout(30000);

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 200) {
            return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        }

        return new Map<String, Object>{
            'verified' => false,
            'error'    => 'HTTP ' + res.getStatusCode() + ': ' + res.getBody()
        };
    }

    /**
     * Query Certyo for the anchoring status of a record.
     *
     * @param recordId The Certyo recordId
     * @return The first matching snapshot or null
     */
    public static Map<String, Object> queryRecordStatus(String recordId) {
        String tenantId = getTenantId();

        HttpRequest req = new HttpRequest();
        req.setEndpoint(NAMED_CREDENTIAL + QUERY_PATH +
            '?tenantId=' + EncodingUtil.urlEncode(tenantId, 'UTF-8') +
            '&recordId=' + EncodingUtil.urlEncode(recordId, 'UTF-8'));
        req.setMethod('GET');
        req.setTimeout(15000);

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 200) {
            Map<String, Object> data =
                (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            List<Object> items = (List<Object>) data.get('items');
            if (items != null && !items.isEmpty()) {
                return (Map<String, Object>) items[0];
            }
        }

        return null;
    }
}

Autenticación: credenciales de cliente OAuth 2.0

Para integraciones servidor a servidor (por ejemplo, middleware externo que llama tanto a Salesforce como a Certyo), usa el flujo de credenciales de cliente OAuth 2.0 para Salesforce y una clave API para Certyo:

Salesforce OAuth 2.0 token requestbash
# 1. Get a Salesforce access token
curl -X POST https://login.salesforce.com/services/oauth2/token \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CONNECTED_APP_CLIENT_ID" \
  -d "client_secret=YOUR_CONNECTED_APP_CLIENT_SECRET"

# Response:
# {
#   "access_token": "00D...",
#   "instance_url": "https://yourorg.my.salesforce.com",
#   "token_type": "Bearer"
# }

# 2. Query Salesforce Orders
curl https://yourorg.my.salesforce.com/services/data/v60.0/query/ \
  -H "Authorization: Bearer 00D..." \
  --data-urlencode "q=SELECT Id, OrderNumber, TotalAmount, Status FROM Order WHERE Status = 'Activated'"

# 3. Ingest into Certyo
curl -X POST https://www.certyos.com/api/v1/records \
  -H "X-API-Key: certyo_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "tenantId": "acme-corp", "database": "salesforce", ... }'

Consideraciones de límites de tasa

Salesforce impone varios límites de gobernanza que afectan el diseño de la integración:

LímiteValorMitigación
Callouts por transacción100Usa @future o Queueable — un callout por ejecución asíncrona
Timeout de callout120s (max)Certyo retorna 202 en <500ms — bien dentro de los límites
Máximo de llamadas @future por transacción50Agrupa eventos y usa encadenamiento de Queueable para >50 registros
Límite de publicación de Platform Event10,000/hour (High Volume)Suficiente para la mayoría de casos; usa Change Data Capture para mayor volumen
Límite diario de Apex asíncrono250,000 or # licenses x 200Monitorea vía Setup > Apex Jobs; usa patrones de procesamiento masivo

Custom Setting: Certyo_Settings__c

Crea un Hierarchy Custom Setting vía Setup > Custom Settings > New para almacenar el ID de tenant y otros valores de configuración que el lee en tiempo de ejecución: CertyoService reads at runtime:

  • Tenant_Id__cText(255). Tu identificador de tenant de Certyo.
  • Database_Name__cText(255). Por defecto . salesforce.
  • Verification_Enabled__cCheckbox. Activa o desactiva el trabajo de verificación programado.

Configura los valores predeterminados de la organización vía Setup > Custom Settings > Certyo_Settings__c > Manage > New (Org Default).

Cobertura de pruebas
Salesforce requiere 75% de cobertura de pruebas Apex para el despliegue. Escribe clases de prueba que simulen el callout HTTP de Certyo usando HttpCalloutMock. Los métodos de CertyoService están diseñados para ser probables — inyecta respuestas simuladas vía Test.setMock(HttpCalloutMock.class, ...) en tu configuración de prueba.

Lista de verificación de despliegue

  1. Crea el objeto personalizado con todos los campos listados arriba Certificate__c custom object with all fields listed above
  2. Crea el Hierarchy Custom Setting y configura los valores predeterminados de la organización Certyo_Settings__c Hierarchy Custom Setting and set org defaults
  3. Crea la Named Credential con tu clave API de producción Certyo_API with your production API key
  4. Define el Platform Event Certyo_Record_Ingest__e
  5. Deploy CertyoService.cls, OrderCertyoTrigger.trigger, and CertyoRecordIngestTrigger.trigger
  6. Deploy CertyoVerificationScheduler.cls and CertyoVerificationJob.cls
  7. Programa el trabajo de verificación: System.schedule('Certyo Verification', '0 0/15 * * * ?', new CertyoVerificationScheduler())
  8. Agrega la lista relacionada al diseño de página del pedido Certificate__c related list to the Order page layout
  9. Prueba de extremo a extremo: activa un pedido y verifica que el registro Certificate__c transicione de Pending a Anchored en 2 minutos

AI Integration · v1.0.0

AI Integration Skill

Download a skill file that enables AI agents to generate working Salesforce + Certyo integration code for any language or framework.

v1.0.0
What is this?
A markdown file containing Salesforce-specific field mappings, authentication setup, code examples, and integration patterns for Certyo. Drop it into your AI agent's context and ask it to generate integration code.

What's inside

  • AutenticaciónNamed Credentials para almacenamiento de clave API y credenciales de cliente OAuth 2.0
  • ArquitecturaTrigger de pedido → Platform Event → callout @future → actualización de Certificate__c
  • Mapeo de camposCampos de Order, Product y Fulfillment al esquema de registro Certyo
  • Ejemplos de códigoClase Apex CertyoService, trigger Apex, Platform Event, verificación programada
  • Límites de gobernanza100 callouts/transacción, patrones @future y Queueable
  • Objetos personalizadosDefinición de Certificate__c con campos RecordHash, AnchorStatus, PolygonTxHash

How to use

Claude Code

Place the file in your project's .claude/commands/ directory, then use it as a slash command:

# Download the skill file
mkdir -p .claude/commands
curl -o .claude/commands/certyo-salesforce.md \
  https://www.certyos.com/developers/skills/certyo-salesforce-skill.md

# Use it in Claude Code
/certyo-salesforce "Genera un servicio Apex que ingeste registros de pedidos en Certyo"

Cursor / Copilot / Any AI Agent

Add the file to your project root or attach it to a conversation. The AI agent will use the Salesforce-specific patterns, field mappings, and code examples to generate correct integration code.

# Add to your project
curl -o CERTYO_SALESFORCE.md \
  https://www.certyos.com/developers/skills/certyo-salesforce-skill.md

# Then in your AI agent:
"Using the Certyo Salesforce spec in CERTYO_SALESFORCE.md,
 genera un servicio apex que ingeste registros de pedidos en certyo"

CLAUDE.md Context File

Append the skill file to your project's CLAUDE.md so every Claude conversation has Salesforce + Certyo context automatically.

# Append to your project's CLAUDE.md
echo "" >> CLAUDE.md
echo "## Certyo Salesforce Integration" >> CLAUDE.md
cat CERTYO_SALESFORCE.md >> CLAUDE.md