Skip to main contentCertyo Developer Portal

SAP S/4HANA & Business One

Integrate SAP S/4HANA (Cloud and On-Premise) and SAP Business One with Certyo to anchor material documents, goods receipts, and serial/batch data on the blockchain.

Overview

SAP environments vary widely in deployment model and API surface. This guide covers production-ready patterns for the two most common SAP products:

  • SAP S/4HANAUses SAP Integration Suite (Cloud Integration) with iFlows to transform material document events into Certyo records. Works with both S/4HANA Cloud and on-premise via Cloud Connector.
  • SAP Business OneUses the Service Layer REST API and B1if (Integration Framework) or direct HTTP calls to push Goods Receipts and inventory events to Certyo.
SAP recommends OData/REST for new integrations
SAP has officially recommended OData and REST APIs over legacy RFC and IDoc interfaces for all new integration projects. All patterns in this guide use the modern API surface. If you have existing IDoc-based integrations, consider migrating them to OData event subscriptions.

Prerequisites

  • SAP BTP (Business Technology Platform) account with Integration Suite entitlement
  • SAP S/4HANA system (Cloud or on-premise with Cloud Connector) or SAP Business One 10.0+
  • Certyo API key from the backoffice (certyo_sk_live_...)
  • Communication Arrangement in S/4HANA for the SAP_COM_0A08 (Material Document) or relevant Business Event scenario
  • For Business One: Service Layer access with a dedicated integration user

Architecture

The recommended architecture uses SAP Integration Suite as the middleware layer. S/4HANA publishes events, the iFlow transforms the SAP-specific payload into the Certyo record format, and the HTTP adapter delivers it:

SAP Integration Architecturebash
┌─────────────────────┐     ┌──────────────────────┐     ┌──────────────────┐     ┌──────────────┐
│  SAP S/4HANA        │     │  SAP Integration      │     │  HTTP Adapter    │     │  Certyo API  │
│                     │────>│  Suite                 │────>│  (outbound)      │────>│              │
│  Material Document  │     │                        │     │                  │     │  POST        │
│  posted (MIGO)      │     │  iFlow:                │     │  TLS 1.2+        │     │  /api/v1/    │
│  or Goods Receipt   │     │  SAP-to-Certyo         │     │  X-API-Key       │     │  records     │
│                     │     │  Transformation        │     │  header          │     │              │
└─────────────────────┘     └──────────────────────┘     └──────────────────┘     └──────────────┘
                                      │
    SAP Business One:                 │  For S/4HANA on-premise:
    Service Layer webhook  ──────────>│  SAP Cloud Connector
    or B1if scenario       ──────────>│  tunnels the connection
                                      │  to BTP securely

S/4HANA Integration

SAP S/4HANA exposes material document events through its Enterprise Event Enablement framework. When a goods receipt (MIGO transaction) or goods issue is posted, the system can publish an event to SAP Integration Suite via the Event Mesh or direct webhook.

Step 1: Configure the Communication Arrangement

In S/4HANA, create a Communication Arrangement using scenario SAP_COM_0A08 (Material Document - Created) or SAP_COM_0A09 (Material Document - Changed). Point the outbound configuration to your Integration Suite tenant.

Step 2: Create the iFlow

In SAP Integration Suite, create an integration flow (iFlow) that receives the S/4HANA event, extracts the material document fields, transforms them into the Certyo record schema, and sends them via HTTP adapter.

Step 3: Deploy and monitor

Deploy the iFlow and monitor message processing in the Integration Suite monitoring dashboard. Failed messages are automatically retried with exponential backoff.


Business One Integration

SAP Business One provides the Service Layer (REST/OData) for programmatic access. The recommended pattern uses the B1if Integration Framework to trigger on document events, or a custom application that polls the Service Layer.

Service Layer event pattern

Business One 10.0+ supports Service Layer Alerts. Configure an alert on GoodsReceiptDocument creation, which calls a webhook URL. Route the webhook through B1if or directly to an intermediary service that calls Certyo.

Polling pattern

If alerts are not available, poll the Service Layer PurchaseDeliveryNotes endpoint with a $filter on UpdateDate. A cron job or scheduled function fetches new documents every 30 seconds and forwards them to Certyo.


Code Samples

This Groovy script runs inside an SAP Integration Suite iFlow. It receives the S/4HANA material document event payload (OData JSON), extracts the relevant fields, and transforms them into the Certyo record format.

SapToCertyoTransform.groovy (runs in SAP CPI iFlow)javascript
import com.sap.gateway.ip.core.customdev.util.Message
import groovy.json.JsonSlurper
import groovy.json.JsonOutput

def Message processData(Message message) {
    def body = message.getBody(String)
    def slurper = new JsonSlurper()
    def sapEvent = slurper.parseText(body)

    // Extract material document header fields
    def matDoc = sapEvent.d ?: sapEvent
    def materialDocument = matDoc.MaterialDocument ?: matDoc.MaterialDocumentNumber
    def materialDocumentYear = matDoc.MaterialDocumentYear ?: new Date().format("yyyy")
    def postingDate = matDoc.PostingDate ?: matDoc.DocumentDate
    def headerText = matDoc.MaterialDocumentHeaderText ?: ""

    // Extract line items
    def items = matDoc.to_MaterialDocumentItem?.results ?: matDoc.Items ?: []
    def recordLines = items.collect { item ->
        [
            itemNumber       : item.MaterialDocumentItem ?: item.LineNumber,
            material         : item.Material ?: item.ItemCode,
            plant            : item.Plant ?: item.Warehouse,
            storageLocation  : item.StorageLocation ?: "",
            batch            : item.Batch ?: "",
            serialNumber     : item.SerialNumber ?: "",
            quantity         : item.QuantityInEntryUnit ?: item.Quantity,
            entryUnit        : item.EntryUnit ?: item.UnitOfMeasure,
            movementType     : item.GoodsMovementType ?: item.MovementType,
            purchaseOrder    : item.PurchaseOrder ?: "",
            purchaseOrderItem: item.PurchaseOrderItem ?: ""
        ]
    }

    // Build Certyo record payload
    def tenantId = message.getProperty("CertyoTenantId")
    def certyoRecord = [
        tenantId       : tenantId,
        database       : "sap-s4hana",
        collection     : "MaterialDocuments",
        recordId       : "${materialDocument}-${materialDocumentYear}",
        recordVersion  : "1",
        operationType  : "insert",
        sourceTimestamp : postingDate,
        idempotencyKey : "${materialDocument}-${materialDocumentYear}-${postingDate?.take(10) ?: 'unknown'}",
        recordPayload  : [
            materialDocument     : materialDocument,
            materialDocumentYear : materialDocumentYear,
            postingDate          : postingDate,
            headerText           : headerText,
            documentType         : matDoc.MaterialDocumentType ?: "WE",
            referenceDocument    : matDoc.ReferenceDocument ?: "",
            items                : recordLines
        ]
    ]

    message.setBody(JsonOutput.toJson(certyoRecord))
    message.setHeader("Content-Type", "application/json")
    message.setHeader("X-API-Key", message.getProperty("CertyoApiKey"))

    return message
}

SAP Integration Suite iFlow Configuration

When deploying the Groovy script within an iFlow, configure the following artifacts:

Sender adapter (S/4HANA)

iFlow sender channel configurationjson
{
  "adapterType": "OData",
  "connectionType": "Internet",
  "address": "https://my-s4hana.example.com/sap/opu/odata/sap/API_MATERIAL_DOCUMENT_SRV",
  "authType": "OAuth2ClientCredentials",
  "credentialName": "S4HANA_OAUTH_CREDENTIALS",
  "entitySet": "A_MaterialDocumentHeader",
  "expand": "to_MaterialDocumentItem",
  "deltaToken": true,
  "pageSize": 100
}

Receiver adapter (Certyo)

iFlow receiver HTTP adapterjson
{
  "adapterType": "HTTP",
  "address": "https://www.certyos.com/api/v1/records",
  "method": "POST",
  "authenticationType": "None",
  "requestHeaders": [
    { "name": "X-API-Key", "value": "${property.CertyoApiKey}" },
    { "name": "Content-Type", "value": "application/json" }
  ],
  "timeout": 15000,
  "retryPolicy": {
    "maxRetries": 3,
    "retryInterval": 5000,
    "exponentialBackoff": true
  }
}

Externalized parameters

Store credentials securely using SAP Integration Suite's Security Material artifacts. The Certyo API key should be stored as a Secure Parameter and referenced in the iFlow:

# Externalized parameters in the iFlow
CertyoApiKey     = {{SecureParameter:CERTYO_API_KEY}}
CertyoTenantId   = your-tenant-id
SapOAuthTokenUrl = https://my-s4hana.example.com/sap/bc/sec/oauth2/token

Authentication

SAP side

SAP supports multiple authentication methods depending on the deployment:

  • S/4HANA Cloud: OAuth 2.0 client credentials via Communication Arrangements. Create a Communication System and Communication User, then generate OAuth credentials.
  • S/4HANA On-Premise: Basic Authentication or X.509 client certificates through SAP Cloud Connector. The Cloud Connector establishes a secure tunnel from BTP to your on-premise system.
  • Business One: Session-based authentication via the Service Layer POST /b1s/v1/Login endpoint. Use a dedicated integration user with minimal authorizations.
SAP Business One Service Layer loginbash
# Authenticate to SAP Business One Service Layer
curl -X POST "https://b1-server:50000/b1s/v1/Login" \
  -H "Content-Type: application/json" \
  -d '{
    "CompanyDB": "SBODEMOUS",
    "UserName": "certyo_integration",
    "Password": "'"$SAP_B1_PASSWORD"'"
  }'

# Response includes a session cookie (B1SESSION) for subsequent requests
# Use this session to query GoodsReceiptDocuments:
curl "https://b1-server:50000/b1s/v1/PurchaseDeliveryNotes?\$filter=UpdateDate ge '2026-04-14'" \
  -H "Cookie: B1SESSION=<session-id>" \
  -H "Accept: application/json"

Certyo side

Certyo uses the X-API-Key header. In SAP Integration Suite, store the key as a Secure Parameter. In custom applications, use environment variables or the SAP Credential Store on BTP:

# Store Certyo API key in SAP BTP Credential Store
cf create-service credstore standard certyo-credentials
cf bind-service certyo-sap-integration certyo-credentials \
  -c '{"key": "certyo-api-key", "value": "certyo_sk_live_abc123..."}'

GS1 EPCIS Alignment

SAP material numbers, batch numbers, and serial numbers map directly to GS1 EPCIS identifiers, enabling supply chain interoperability. When building your Certyo record payload, include these mappings:

EPCIS-aligned record payloadjson
{
  "tenantId": "pharma-corp",
  "database": "sap-s4hana",
  "collection": "MaterialDocuments",
  "recordId": "4900000123-2026",
  "recordPayload": {
    "materialDocument": "4900000123",
    "materialDocumentYear": "2026",
    "postingDate": "2026-04-14",
    "items": [
      {
        "material": "MAT-001234",
        "batch": "BATCH-2026-Q2-001",
        "serialNumber": "SN-00001",
        "quantity": 500,
        "entryUnit": "EA",
        "movementType": "101",
        "epcis": {
          "gtin": "01234567890128",
          "sgtin": "urn:epc:id:sgtin:0123456.078901.SN-00001",
          "lot": "BATCH-2026-Q2-001",
          "bizStep": "urn:epcglobal:cbv:bizstep:receiving",
          "disposition": "urn:epcglobal:cbv:disp:in_progress",
          "readPoint": "urn:epc:id:sgln:0123456.00001.0"
        }
      }
    ]
  }
}

This mapping enables downstream consumers of your Certyo certificates to correlate them with GS1 EPCIS events, which is critical for pharmaceutical serialization (DSCSA), food safety (FSMA 204), and luxury goods traceability.


Verification & SAP Write-Back

After Certyo anchors the record (60-90 seconds), verify it and update the SAP Quality Inspection record or a custom Z-table with the certificate details. This example uses the S/4HANA OData API to update a custom field on the material document:

verify_and_writeback.pypython
"""
Verify Certyo records and write certificate data back to SAP S/4HANA.
Runs as a scheduled job (e.g., every 2 minutes via cron or SAP BTP Job Scheduler).
"""

import os
import time
import requests
from requests.auth import HTTPBasicAuth

CERTYO_BASE_URL = os.environ.get("CERTYO_BASE_URL", "https://www.certyos.com")
CERTYO_API_KEY = os.environ["CERTYO_API_KEY"]
CERTYO_TENANT_ID = os.environ["CERTYO_TENANT_ID"]
SAP_BASE_URL = os.environ["SAP_BASE_URL"]
SAP_USERNAME = os.environ["SAP_USERNAME"]
SAP_PASSWORD = os.environ["SAP_PASSWORD"]


def get_csrf_token() -> tuple[str, requests.Session]:
    """Fetch CSRF token from SAP for write operations."""
    session = requests.Session()
    session.auth = HTTPBasicAuth(SAP_USERNAME, SAP_PASSWORD)
    response = session.head(
        f"{SAP_BASE_URL}/sap/opu/odata/sap/API_MATERIAL_DOCUMENT_SRV",
        headers={"X-CSRF-Token": "Fetch"},
        timeout=10,
    )
    csrf_token = response.headers.get("X-CSRF-Token", "")
    return csrf_token, session


def verify_and_write_back():
    """Query Certyo for anchored records and write verification back to SAP."""
    # 1. Query Certyo for recently anchored records
    query_response = requests.get(
        f"{CERTYO_BASE_URL}/api/v1/records",
        params={
            "tenantId": CERTYO_TENANT_ID,
            "database": "sap-s4hana",
            "status": "Anchored",
            "limit": 50,
        },
        headers={"X-API-Key": CERTYO_API_KEY},
        timeout=15,
    )
    query_response.raise_for_status()
    records = query_response.json().get("items", [])

    if not records:
        print("No anchored records to verify")
        return

    csrf_token, sap_session = get_csrf_token()

    for record in records:
        record_id = record["recordId"]
        payload_data = record.get("recordPayload", {})

        # 2. Verify the record on-chain
        verify_response = requests.post(
            f"{CERTYO_BASE_URL}/api/v1/verify/record",
            headers={
                "X-API-Key": CERTYO_API_KEY,
                "Content-Type": "application/json",
            },
            json={
                "tenantId": CERTYO_TENANT_ID,
                "database": "sap-s4hana",
                "collection": "MaterialDocuments",
                "recordId": record_id,
                "payload": payload_data,
            },
            timeout=15,
        )
        verification = verify_response.json()

        if not verification.get("verified"):
            print(f"Verification failed for {record_id}: {verification.get('reasonCategory')}")
            continue

        # 3. Write certificate data back to SAP Quality Inspection
        mat_doc = payload_data.get("materialDocument", "")
        mat_doc_year = payload_data.get("materialDocumentYear", "")

        # Update custom Z-fields via SAP OData PATCH
        sap_session.patch(
            f"{SAP_BASE_URL}/sap/opu/odata/sap/ZCERTYO_CERTIFICATE_SRV/CertificateSet(MaterialDocument='{mat_doc}',MaterialDocumentYear='{mat_doc_year}')",
            json={
                "CertyoVerified": True,
                "CertyoRecordHash": verification.get("snapshotHash", ""),
                "CertyoMerkleRoot": verification.get("merkleRoot", ""),
                "CertyoPolygonTx": verification.get("onChainProof", {}).get("polygonScanEventUrl", ""),
                "CertyoIpfsCid": verification.get("ipfsEvidence", {}).get("ipfsCid", ""),
                "CertyoVerifiedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            },
            headers={
                "X-CSRF-Token": csrf_token,
                "Content-Type": "application/json",
                "If-Match": "*",
            },
            timeout=10,
        )
        print(
            f"Wrote certificate to SAP for {mat_doc}/{mat_doc_year}: "
            f"{verification['onChainProof']['polygonScanEventUrl']}"
        )


if __name__ == "__main__":
    verify_and_write_back()
SAP custom OData service required
The write-back example uses a custom OData service (ZCERTYO_CERTIFICATE_SRV). You need to create this as a custom CDS view or SEGW project in S/4HANA, exposing the Z-fields for certificate data. For Business One, use User-Defined Fields (UDFs) on the relevant document type instead.

Testing

  1. Configure the SAP Integration Suite iFlow with your Certyo sandbox key and deploy to a test tenant. (certyo_sk_test_...)
  2. Post a Goods Receipt in S/4HANA (transaction MIGO, movement type 101) or create a Purchase Delivery Note in Business One.
  3. Monitor the iFlow execution in SAP Integration Suite > Monitor > Message Processing. Confirm the Certyo call returned 202 Accepted and a recordHash.
  4. Wait 60-90 seconds for anchoring, then call POST /api/v1/verify/record with the material document data to confirm on-chain anchoring.
  5. Run the verification write-back script and confirm the certificate data appears in the SAP custom fields or Z-table.

Troubleshooting

  • 401 from S/4HANA ODataCheck that the Communication Arrangement is active and the OAuth credentials have not expired. For on-premise, verify the Cloud Connector tunnel is up.
  • Empty items array in CertyoEnsure the OData $expand parameter includes to_MaterialDocumentItem. Without it, the line items are not fetched.
  • Duplicate records in CertyoThe idempotencyKey pattern uses materialDocument-year-postingDate. If you repost the same document on the same day, Certyo returns the original response with idempotencyReplayed: true.
  • CSRF token errors on SAP write-backAlways fetch a fresh CSRF token before PATCH/POST operations. Tokens expire after the SAP session timeout (default 30 minutes).

AI Integration · v1.0.0

AI Integration Skill

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

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

  • AuthenticationOAuth 2.0 / X.509 via SAP BTP and Integration Suite credentials
  • ArchitectureMaterial document → SAP Integration Suite iFlow → HTTP adapter → Certyo
  • Field mappingMATNR, CHARG, SERNR, BWART to Certyo fields with GS1 EPCIS alignment
  • Code examplesGroovy iFlow transform, JavaScript CAP handler, Python OData poller
  • VerificationOData PATCH with CSRF token handling for write-back to SAP
  • Integration patternsS/4HANA Cloud via Integration Suite, on-premise via Cloud Connector

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

# Use it in Claude Code
/certyo-sap "Generate an SAP Integration Suite iFlow that sends material documents to 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 SAP-specific patterns, field mappings, and code examples to generate correct integration code.

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

# Then in your AI agent:
"Using the Certyo SAP spec in CERTYO_SAP.md,
 generate an sap integration suite iflow that sends material documents to certyo"

CLAUDE.md Context File

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

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