Skip to main contentCertyo Developer Portal

Integración con Oracle NetSuite

Conecta Oracle NetSuite a Certyo usando RESTlets, User Event Scripts y la API REST de SuiteTalk. Ancla automáticamente datos de cumplimiento de artículos, movimientos de inventario y registros de órdenes de venta en la blockchain.

Requisitos previos

  • Cuenta de Oracle NetSuite con SuiteCloud habilitado (rol de Administrador o Desarrollador)
  • Token-Based Authentication (TBA) configurada en Setup > Company > Enable Features > SuiteCloud > Manage Authentication
  • Una clave API de Certyo — consulta la Guía de autenticación
  • Runtime SuiteScript 2.x (NetSuite 2020.1+)

Arquitectura

Integration flowbash
┌─────────────────────────┐
│   Oracle NetSuite       │
│                         │
│  Item Fulfillment       │
│  record saved           │
│         │               │
│         v               │
│  User Event Script      │
│  (afterSubmit)          │
│         │               │
│         v               │
│  RESTlet / N/https      │──────────┐
│  module                 │          │
└─────────────────────────┘          │
                                     │  HTTPS POST
                                     │  X-API-Key header
                                     v
                          ┌─────────────────────┐
                          │  Certyo API          │
                          │  POST /api/v1/records│
                          │                      │
                          │  202 Accepted        │
                          └──────────┬───────────┘
                                     │
                                     v
                          ┌─────────────────────┐
                          │  Pipeline            │
                          │  Kafka -> Accumulate │
                          │  -> Merkle Tree      │
                          │  -> IPFS -> Polygon  │
                          └─────────────────────┘

Configuración de autenticación

NetSuite usa Token-Based Authentication (TBA) para acceso servidor a servidor. Necesitas cuatro credenciales de Setup > Integration > Manage Integrations:

  • Consumer Key / Consumer Secret — Consumer Key y Consumer Secret (del registro de integración)
  • Token ID / Token Secret — Token ID y Token Secret (del token de acceso asignado a un rol)

Para el lado de Certyo, almacena tu clave API como un parámetro de script o en un registro de configuración personalizado — nunca la codifiques directamente en archivos fuente de SuiteScript.

Mapeo de campos

Campo de NetSuiteCampo de CertyoNotas
tranId (Item Fulfillment)recordIdNúmero único de cumplimiento, por ejemplo IF-10042
subsidiary.nametenantIdSe mapea a tu tenant de Certyo
"netsuite"databaseValor estático que identifica el sistema de origen
"item_fulfillments"collectionTipo de transacción como nombre de colección
tranDatesourceTimestampConvertir a ISO 8601 (por ejemplo 2026-04-14T00:00:00Z)
item[].itemId, quantity, serialNumbersrecordPayloadDetalles de las líneas de cumplimiento
createdDate + tranIdidempotencyKeyPreviene ingesta duplicada en reintentos
"1"recordVersionIncrementar en actualizaciones posteriores
"insert"operationTypeUsar "update" para cumplimientos modificados

Implementación

Límite de concurrencia de RESTlet
NetSuite permite un máximo de 10 ejecuciones concurrentes de RESTlet por cuenta. Para operaciones masivas (carga inicial, importaciones masivas), usa un script Map/Reduce de SuiteScript en lugar de llamadas individuales al RESTlet.

Despliega este RESTlet para recibir datos de cumplimiento de sistemas externos o llamadas internas de SuiteScript, y reenviarlos a Certyo.

certyo_restlet.js — SuiteScript 2.x RESTletjavascript
/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 * @NModuleScope SameAccount
 *
 * Certyo Integration RESTlet
 * Accepts item fulfillment data and ingests it into Certyo for
 * blockchain anchoring.
 *
 * Deploy: Setup > Scripting > Scripts > New > RESTlet
 * URL:    /app/site/hosting/restlet.nl?script=<id>&deploy=1
 */
define(['N/https', 'N/log', 'N/runtime'], function (https, log, runtime) {

  var CERTYO_API_URL = 'https://www.certyos.com/api/v1/records';

  /**
   * POST handler — accepts a fulfillment payload and sends it to Certyo.
   * @param {Object} requestBody - The fulfillment data from the caller
   * @returns {Object} Certyo API response or error details
   */
  function post(requestBody) {
    var scriptObj = runtime.getCurrentScript();
    var apiKey = scriptObj.getParameter({ name: 'custscript_certyo_api_key' });

    if (!apiKey) {
      log.error('Configuration Error', 'custscript_certyo_api_key parameter is not set');
      return { success: false, error: 'API key not configured' };
    }

    try {
      var certRecord = {
        tenantId:        requestBody.subsidiary || 'default',
        database:        'netsuite',
        collection:      'item_fulfillments',
        recordId:        requestBody.tranId,
        recordVersion:   String(requestBody.version || '1'),
        operationType:   requestBody.operationType || 'insert',
        recordPayload: {
          tranId:          requestBody.tranId,
          entity:          requestBody.entity,
          entityName:      requestBody.entityName,
          tranDate:        requestBody.tranDate,
          shipMethod:      requestBody.shipMethod,
          trackingNumbers: requestBody.trackingNumbers || [],
          items:           requestBody.items || [],
          memo:            requestBody.memo || '',
          location:        requestBody.location,
          createdFrom:     requestBody.createdFrom
        },
        sourceTimestamp:  requestBody.tranDate
          ? new Date(requestBody.tranDate).toISOString()
          : new Date().toISOString(),
        idempotencyKey:   requestBody.tranId + '-v' +
                          (requestBody.version || '1') + '-' +
                          new Date().toISOString().substring(0, 10)
      };

      log.audit('Certyo Ingestion', 'Sending record: ' + certRecord.recordId);

      var response = https.post({
        url: CERTYO_API_URL,
        headers: {
          'X-API-Key':    apiKey,
          'Content-Type': 'application/json',
          'Accept':       'application/json'
        },
        body: JSON.stringify(certRecord)
      });

      var statusCode = response.code;
      var responseBody = JSON.parse(response.body);

      if (statusCode === 202) {
        log.audit('Certyo Ingestion Success', JSON.stringify({
          recordId:   responseBody.recordId,
          recordHash: responseBody.recordHash,
          acceptedAt: responseBody.acceptedAt
        }));
        return {
          success:    true,
          recordHash: responseBody.recordHash,
          acceptedAt: responseBody.acceptedAt
        };
      } else {
        log.error('Certyo Ingestion Failed', 'HTTP ' + statusCode +
                  ': ' + response.body);
        return {
          success:    false,
          statusCode: statusCode,
          error:      responseBody.message || response.body
        };
      }

    } catch (e) {
      log.error('Certyo Ingestion Error', e.message + '\n' + e.stack);
      return { success: false, error: e.message };
    }
  }

  return { post: post };
});

Verificación: Script programado

Después de que los registros son ingestados, tardan aproximadamente 60-90 segundos en ser anclados en Polygon. Despliega un Scheduled Script que se ejecute cada 15 minutos para consultar el estado de anclaje en Certyo y actualizar un campo personalizado en el registro Item Fulfillment.

certyo_ss_verify.js — SuiteScript 2.x Scheduled Scriptjavascript
/**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 * @NModuleScope SameAccount
 *
 * Certyo Verification Poller
 * Runs on a schedule to check anchoring status of pending records
 * and update the Item Fulfillment with verification results.
 *
 * Deploy: Setup > Scripting > Script Deployments
 *   Schedule: Every 15 minutes
 */
define(['N/https', 'N/search', 'N/record', 'N/log', 'N/runtime'],
  function (https, search, record, log, runtime) {

  var CERTYO_VERIFY_URL = 'https://www.certyos.com/api/v1/verify/record';
  var CERTYO_QUERY_URL  = 'https://www.certyos.com/api/v1/records';

  function execute(context) {
    var scriptObj = runtime.getCurrentScript();
    var apiKey = scriptObj.getParameter({ name: 'custscript_certyo_api_key' });
    var tenantId = scriptObj.getParameter({ name: 'custscript_certyo_tenant_id' });

    // Find fulfillments with Pending Certyo status
    var pendingSearch = search.create({
      type: search.Type.ITEM_FULFILLMENT,
      filters: [
        ['custbody_certyo_status', 'is', 'Pending'],
        'AND',
        ['custbody_certyo_record_hash', 'isnotempty', ''],
        'AND',
        ['mainline', 'is', 'T']
      ],
      columns: [
        search.createColumn({ name: 'tranid' }),
        search.createColumn({ name: 'custbody_certyo_record_hash' })
      ]
    });

    var results = [];
    pendingSearch.run().each(function (result) {
      results.push({
        internalId: result.id,
        tranId:     result.getValue('tranid'),
        recordHash: result.getValue('custbody_certyo_record_hash')
      });
      return results.length < 200; // Process up to 200 per run
    });

    log.audit('Certyo Verify', 'Found ' + results.length + ' pending records');

    for (var i = 0; i < results.length; i++) {
      if (runtime.getCurrentScript().getRemainingUsage() < 100) {
        log.audit('Certyo Verify', 'Low governance — stopping at record ' + i);
        break;
      }

      var item = results[i];
      try {
        // Query record status
        var queryResp = https.get({
          url: CERTYO_QUERY_URL +
               '?tenantId=' + encodeURIComponent(tenantId) +
               '&recordId=' + encodeURIComponent(item.tranId),
          headers: { 'X-API-Key': apiKey }
        });

        if (queryResp.code !== 200) continue;

        var queryData = JSON.parse(queryResp.body);
        var snapshot = (queryData.items || [])[0];
        if (!snapshot || snapshot.status !== 'Anchored') continue;

        // Record is anchored — update the fulfillment
        record.submitFields({
          type: record.Type.ITEM_FULFILLMENT,
          id:   item.internalId,
          values: {
            'custbody_certyo_status':       'Anchored',
            'custbody_certyo_polygon_tx':   snapshot.polygonTxHash || '',
            'custbody_certyo_verified_at':  new Date().toISOString()
          },
          options: { enableSourcing: false, ignoreMandatoryFields: true }
        });

        log.audit('Certyo Verified', item.tranId + ' -> Anchored');

      } catch (e) {
        log.error('Certyo Verify Error', item.tranId + ': ' + e.message);
      }
    }
  }

  return { execute: execute };
});

Configuración de campos personalizados

Crea estos campos personalizados en el tipo de registro Item Fulfillment vía Customization > Lists, Records, & Fields > Transaction Body Fields:

  • custbody_certyo_record_hash — Free-form Text, solo almacenamiento. Contiene el hash SHA-256 retornado por Certyo en la ingesta.
  • custbody_certyo_status — List/Record (lista personalizada con valores: Pending, Anchored, Failed). Se muestra en el formulario de cumplimiento.
  • custbody_certyo_polygon_tx — Free-form Text, solo almacenamiento. El hash de transacción en Polygon una vez anclado.
  • custbody_certyo_verified_at — Date/Time, solo almacenamiento. Marca de tiempo de la última verificación exitosa.

Parámetros de script

Agrega un parámetro de script a cada despliegue de SuiteScript para almacenar de forma segura la clave API de Certyo:

  • ID: custscript_certyo_api_key
  • Type: Password (o Free-Form Text si Password no está disponible)
  • Description: Clave API de Certyo para anclaje de registros en blockchain

Para el Scheduled Script, también agrega:

  • ID: custscript_certyo_tenant_id
  • Type: Free-Form Text
  • Description: Identificador de tenant de Certyo para consultas al API
Alternativa SuiteQL
Si tu equipo prefiere consultar datos de NetSuite desde un servicio externo en lugar de desplegar SuiteScript, usa la API REST de SuiteTalk con SuiteQL (mostrado en la pestaña de Python). Este enfoque evita los límites de gobernanza de NetSuite y es más adecuado para cargas iniciales a gran escala.

AI Integration · v1.0.0

AI Integration Skill

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

v1.0.0
What is this?
A markdown file containing Oracle NetSuite-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

  • Authentication — Token-Based Auth (TBA) y OAuth 2.0 para acceso al API de NetSuite
  • Architecture — afterSubmit de Item Fulfillment → módulo N/https → API de Certyo
  • Field mapping — Campos de tranid, trandate y artículo de cumplimiento al esquema de registro Certyo
  • Code examples — RESTlet SuiteScript 2.x, User Event Script, sincronización por lotes en Python con SuiteQL
  • Verification — Scheduled Script con polling y procesamiento consciente de gobernanza
  • Concurrency — Límite de RESTlet de 10 y patrones Map/Reduce para operaciones masivas

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-netsuite.md \
  https://www.certyos.com/developers/skills/certyo-netsuite-skill.md

# Use it in Claude Code
/certyo-netsuite "Genera un User Event de SuiteScript que envíe artículos cumplidos a 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 Oracle NetSuite-specific patterns, field mappings, and code examples to generate correct integration code.

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

# Then in your AI agent:
"Using the Certyo Oracle NetSuite spec in CERTYO_NETSUITE.md,
 genera un user event de suitescript que envíe artículos cumplidos a certyo"

CLAUDE.md Context File

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

# Append to your project's CLAUDE.md
echo "" >> CLAUDE.md
echo "## Certyo Oracle NetSuite Integration" >> CLAUDE.md
cat CERTYO_NETSUITE.md >> CLAUDE.md