Skip to main contentCertyo Developer Portal

Integrazione Salesforce

Collega Salesforce a Certyo usando Platform Events, callout Apex e oggetti personalizzati. Ancora automaticamente i dati di evasione ordini sulla blockchain e traccia lo stato del certificato direttamente in Salesforce.

Prerequisiti

  • Salesforce Enterprise o Unlimited edition (Platform Events e Named Credentials richiedono queste edizioni)
  • Una chiave API Certyo — vedi la guida all autenticazione
  • Amministratore di sistema o un permission set personalizzato con accesso a classi Apex, Platform Events e Named Credentials
  • My Domain abilitato (richiesto per Named Credentials)

Architettura

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  │
                                      └──────────────────┘

Oggetto personalizzato: Certificate__c

Crea un oggetto personalizzato per tracciare il ciclo di vita del certificato blockchain per ogni record. Vai a Setup > Object Manager > Create > Custom Object.

Nome API campoTipoDescrizione
RecordId__cText(255)Il recordId Certyo (es. il numero d ordine)
RecordHash__cText(255)Hash SHA-256 restituito da Certyo all acquisizione
SnapshotId__cText(255)ID snapshot Certyo che contiene questo record
MerkleRoot__cText(255)Hash radice dell albero Merkle per il batch di ancoraggio
AnchorStatus__cPicklistPending | Batched | Anchored | Failed
PolygonTxHash__cText(255)Hash della transazione Polygon una volta ancorato on-chain
IpfsCid__cText(255)Identificatore di contenuto IPFS per il manifesto
VerifiedAt__cDateTimeTimestamp dell ultima verifica riuscita
Order__cLookup(Order)Record ordine correlato per navigazione e reportistica

Named Credential

Conserva l endpoint API Certyo e l header di autenticazione in un Named Credential, cosi il codice Apex non gestisce mai segreti in chiaro. Vai 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
Limiti callout
Salesforce impone un limite di 100 callout per transazione Apex. Usa Platform Events con @future(callout=true) o Queueable Apex per effettuare callout al di fuori del contesto della transazione trigger. Non effettuare mai callout direttamente nel body di un trigger.

Implementazione

Classe di servizio principale che gestisce sia l acquisizione dei record che la verifica. Usa Named Credentials per l autenticazione, quindi nessuna chiave API appare nel codice.

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;
    }
}

Autenticazione: OAuth 2.0 client credentials

Per integrazioni server-to-server (es. middleware esterno che chiama sia Salesforce che Certyo), usa il flusso OAuth 2.0 Client Credentials per Salesforce e una chiave API per 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", ... }'

Considerazioni sui limiti di frequenza

Salesforce impone diversi governor limits che influenzano il design dell integrazione:

LimiteValoreMitigazione
Callout per transazione100Usa @future o Queueable — un callout per esecuzione asincrona
Timeout callout120s (max)Certyo restituisce 202 in <500ms — ben entro i limiti
Max chiamate @future per transazione50Raggruppa gli eventi e usa il chaining Queueable per >50 record
Limite pubblicazione Platform Event10,000/hour (High Volume)Sufficiente per la maggior parte dei casi d uso; usa Change Data Capture per volumi maggiori
Limite giornaliero Async Apex250,000 or # licenses x 200Monitora tramite Setup > Apex Jobs; usa pattern in blocco

Custom Setting: Certyo_Settings__c

Crea un Hierarchy Custom Setting tramite Setup > Custom Settings > New per memorizzare l ID tenant e altri valori di configurazione che il legge a runtime: CertyoService

  • Tenant_Id__c — Text(255). Il tuo identificatore tenant Certyo.
  • Database_Name__c — Text(255). Predefinito a . salesforce.
  • Verification_Enabled__c — Checkbox. Attiva/disattiva il job di verifica pianificato.

Imposta i valori predefiniti a livello organizzazione tramite Setup > Custom Settings > Certyo_Settings__c > Manage > New (Org Default).

Copertura test
Salesforce richiede il 75% di copertura test Apex per la distribuzione. Scrivi classi di test che simulano il callout HTTP Certyo usando HttpCalloutMock. I metodi CertyoService sono progettati per essere testabili — inietta risposte mock tramite Test.setMock(HttpCalloutMock.class, ...) nel tuo setup di test.

Checklist di distribuzione

  1. Crea l oggetto personalizzato con tutti i campi elencati sopra Certificate__c custom object with all fields listed above
  2. Crea il Hierarchy Custom Setting e imposta i valori predefiniti dell organizzazione Certyo_Settings__c Hierarchy Custom Setting and set org defaults
  3. Crea il Named Credential con la tua chiave API di produzione Certyo_API with your production API key
  4. Definisci il Platform Event Certyo_Record_Ingest__e
  5. Deploy CertyoService.cls, OrderCertyoTrigger.trigger, and CertyoRecordIngestTrigger.trigger
  6. Deploy CertyoVerificationScheduler.cls and CertyoVerificationJob.cls
  7. Pianifica il job di verifica: System.schedule('Certyo Verification', '0 0/15 * * * ?', new CertyoVerificationScheduler())
  8. Aggiungi la lista correlata al layout pagina dell ordine Certificate__c
  9. Testa end-to-end: attiva un ordine e verifica che il record Certificate__c passi da Pending ad Anchored entro 2 minuti

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

  • Autenticazione — Named Credentials per l archiviazione della chiave API e OAuth 2.0 client credentials
  • Architettura — Trigger ordine → Platform Event → callout @future → aggiornamento Certificate__c
  • Mappatura campi — Campi ordine, prodotto ed evasione allo schema record Certyo
  • Esempi di codice — Classe Apex CertyoService, trigger Apex, Platform Event, verifica pianificata
  • Governor limits — 100 callout/transazione, pattern @future e Queueable
  • Oggetti personalizzati — Definizione Certificate__c con campi 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 servizio Apex che acquisisce record ordine in 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 servizio apex che acquisisce record ordine in 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