SAP S/4HANA Warp Adapter Specification
Status: Specification — implementation in Phase 3.
This document specifies how SAP S/4HANA’s business eventing surface translates into Warp’s typed commerce vocabulary. The adapter is not yet implemented; this spec is the engineering brief Phase 3 will execute against.
Overview
SAP S/4HANA is the largest enterprise ERP in the world. A Warp adapter for S/4HANA is the unlock that puts Warp on the same canvas as Tier-1 retail, manufacturing, and B2B commerce — every Fortune 500 running SAP becomes a candidate Warp tenant without touching their SAP installation.
The adapter sits between SAP’s outbound event surface (Business
Eventing on BTP) and Warp’s chain workflows. SAP emits OData /
CloudEvents-shaped webhooks; the adapter validates them, translates
to Warp types, and fires the matching chain (CartRecoveryFull /
PostPurchaseWorkflow).
This adapter is the first one whose source platform is not a storefront. Odoo (Phase 2 session 8) was Warp’s first ERP adapter but Odoo is mid-market; SAP carries enterprise expectations around auth, signing, idempotency, and SLA.
Integration approach
SAP Business Eventing (SAP BTP) is the canonical outbound event surface for S/4HANA. It emits CloudEvents-format envelopes over HTTPS when business objects change. The Warp SAP adapter is a Business Eventing consumer:
SAP S/4HANA (on-prem or cloud)
│
▼ (SAP Cloud Connector or direct, depending on deployment)
SAP Business Technology Platform (BTP)
│
▼ (HTTPS webhook, CloudEvents envelope, OAuth client-credentials)
warp-catalog::adapters::sap::SapEventBridge (Restate service)
│
▼ translate_sap_event(envelope) → CartAbandonedInput | OrderPlacedInput
▼
WORKFLOW_ROUTES → CartRecoveryFull | PostPurchaseWorkflow
Why Business Eventing (not iDoc / RFC). iDoc and RFC are batch-friendly transports inherited from R/3. Business Eventing is the S/4HANA-native real-time path with first-class CloudEvents support, OAuth 2.0 client credentials, and BTP-managed retries — all the affordances Warp’s adapter contract already assumes. Choosing iDoc would force the adapter to also build a sequencing + dedup layer, expanding scope from “translation” to “ESB-lite.”
Event mapping
Warp’s two chain families are the v0.1 targets:
| SAP Event | Warp Type | Notes |
|---|---|---|
SalesOrder.Created.v1 |
OrderPlacedInput |
OrderID prefix sap_{SalesOrder}; routes to PostPurchaseWorkflow |
SalesOrder.Changed.v1 |
(future) | Status transitions — defer until merchants request the path |
BusinessPartner.Changed.v1 |
(future) | Customer-record updates — needs new commerce type before usable |
Only SalesOrder.Created.v1 is in scope for v0.1. The other two are
named here so a future PR can land them additively without revisiting
the namespacing rules.
SalesOrder.Created.v1 payload (CloudEvents envelope)
Drawn from SAP’s published Business Eventing schema for
API_SALES_ORDER_SRV and adjusted for the fields Warp consumes.
Fields not used by Warp are listed for completeness; the adapter
must ignore them rather than reject.
{
"specversion": "1.0",
"type": "sap.s4.beh.salesorder.v1.SalesOrder.Created.v1",
"source": "/default/sap.s4/SALESORDER",
"id": "ev-2026-05-26T10:00:00.000Z-12345",
"time": "2026-05-26T10:00:00.000Z",
"datacontenttype": "application/json",
"data": {
"SalesOrder": "0000123456",
"SoldToParty": "0010000001",
"TransactionCurrency": "MAD",
"TotalNetAmount": "850.00",
"OrderDate": "2026-05-26T10:00:00.000Z",
"SalesOrganization": "1010",
"DistributionChannel": "10",
"OrganizationDivision": "00",
"to_Item": [
{
"SalesOrderItem": "10",
"Material": "MAT-ABC",
"MaterialName": "Sample Product",
"RequestedQuantity": "2",
"RequestedQuantityUnit":"EA",
"NetAmount": "425.00",
"Plant": "1010"
}
]
}
}
The adapter reads data.SalesOrder, data.SoldToParty,
data.TransactionCurrency, data.TotalNetAmount, data.OrderDate,
and data.to_Item (length only). Everything else is ignored at v0.1.
Translation rules
Concretely, the adapter constructs a Translated::OrderPlaced from
the envelope as follows. Each row maps a SAP field to a Warp construction
call — the same shape the Odoo adapter follows in
crates/warp-catalog/src/adapters/odoo/event_bridge.rs (part of Warp’s
commercial distribution, not included in this open-source repository).
| Warp field | Construction | Notes |
|---|---|---|
tenant_id |
from envelope header X-Warp-Tenant-Id (per ADR-0003 v0.2 routing) |
stop-gap envelope field tenant_id accepted until v0.2 lands |
order_id |
OrderID::new(&format!("sap_{}", data.SalesOrder))? |
Rule 1 — namespace the platform id at the boundary |
customer_id |
CustomerID::new(&format!("sap_customer_{}", data.SoldToParty))? |
Rule 1 |
order_value |
Currency::new(Decimal::from_str(&data.TotalNetAmount)?, CurrencyCode::from_str(&data.TransactionCurrency)?) |
Rule 2 — parse to Currency at the boundary, never f64 |
placed_at |
data.OrderDate (already ISO 8601 in CloudEvents) |
Rule 5 — no translation needed |
platform |
Platform::SAP |
requires the enum variant landing first (see “Platform enum” below) |
item_count |
data.to_Item.len() as u32 |
Optional metadata; surfaced for the Post-Purchase template |
delivery_address |
None | SAP carries it on a separate object; v0.2 work |
Rejection paths
The adapter MUST surface each of these as a structured
AdapterError (matching the Odoo / Shopify shape) — never a runtime
panic and never a silent drop:
- Missing or empty
SalesOrder→AdapterError::InvalidOrderID - Missing or empty
SoldToParty→AdapterError::InvalidCustomerID TotalNetAmountnot parseable →AdapterError::InvalidAmountTransactionCurrencynot inMAD | EUR | USD→AdapterError::UnknownCurrency- Envelope
type≠ a known SAP event →AdapterError::UnknownEventType
Every rejection echoes the offending field value in the Display message so operators can grep the SAP outbound log.
Currency support
v0.1 of the Warp type system carries MAD | EUR | USD. The SAP
adapter does NOT silently coerce other currencies — a SAP order in
AED or GBP fails at the adapter with UnknownCurrency and the
adapter author opens a PR to extend CurrencyCode.
This is the same posture as every other adapter; SAP merchants typically run multi-currency configurations and we expect to add codes through that PR pathway during Phase 3 rollout.
Authentication + signing
OAuth 2.0 client credentials (transport)
SAP Business Eventing uses OAuth 2.0 client credentials to authenticate the receiver. The adapter MUST:
- Accept an
Authorization: Bearer <token>header on every inbound webhook. - Validate the token against the SAP BTP destination’s JWKS endpoint (cached with a sensible TTL).
- Reject any request without a valid token with HTTP 401.
The token validation logic lives in warp-server’s pre-handler
middleware (cross-cutting, not per-adapter) so future adapters using
OAuth (Salesforce, Workday) reuse the same gate.
Webhook signature (defense in depth)
SAP signs Business Eventing webhooks with a tenant-scoped HMAC shared secret. The adapter MUST:
- Accept an
X-Sap-Signatureheader carryingsha256=<hex>. - Recompute the signature over the raw request body with the
per-tenant secret (stored in
warp_tenants.platform_config.webhook_secret). - Reject any request whose computed signature does not match, regardless of the OAuth token result.
This is belt-and-braces: if a token leaks, the signature still blocks forged payloads from impersonating a tenant.
Detailed implementation deferred to Phase 3 — landing OAuth + HMAC together with the adapter ensures Day 1 is hardened, not bolted on later.
Idempotency
SAP Business Eventing retries on failure. CloudEvents envelopes carry
a unique id per emission; the adapter MUST:
- Compute a Restate workflow key as
{tenant_id}:{envelope.id}so a retry lands on the same Restate invocation rather than re-running the chain. - Treat replays as no-ops at the chain level — Restate’s exactly-once semantics handle this once the key is shared.
This pattern is already proven by C-05 testing on CartRecoveryFull;
the SAP adapter is structurally identical, the contract just calls it
out explicitly because enterprise auditors ask.
Platform enum
Implementing this adapter requires extending the closed Platform
enum in crates/warp-core/src/types/commerce.rs:
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum Platform {
Agora, Shopify, WooCommerce, OpenCart, Magento, Odoo,
// Phase 3 addition:
SAP,
}
Mirror the change in:
WARP_TYPE_SPEC_v0.1.mdPlatform table — add aSAProw with thesap_prefix convention.- The Platform’s serialization round-trip test in
crates/warp-core/src/types/commerce.rs. - The merchant API’s
AdapterType::SAPvariant (and thewebhook_path_formap incrates/warp-server/src/api/tenants.rs).
These are all single-line additions; the closed-enum design is deliberate so an unsupported platform fails at compile time.
Compatibility surface
Following the WARP_COMPATIBLE_GUIDE:
- Rule 1 (namespace ids): every SAP id prefixed
sap_/sap_customer_at the adapter boundary. - Rule 2 (Currency validation):
TotalNetAmountparsed toDecimaland combined with a validatedCurrencyCode. - Rule 3 (PhoneNumber): SAP’s
SalesOrderpayload doesn’t carry a phone number directly — theBusinessPartner.Changed.v1extension will, and Warp will surface it throughACPGetCustomerProfileuntil then. - Rule 4 (tenant_id on every event): per-tenant webhook URLs
with header-based tenancy in v0.2; v0.1 stop-gap is the envelope
tenant_idfield, same as the other Phase 2 adapters. - Rule 5 (ISO 8601 timestamps): SAP’s
OrderDateis already ISO 8601 in the CloudEvents shape — passthrough.
The adapter passes /api/v1/validate-event against a sample
order.placed payload the moment the translation layer compiles.
Estimated implementation
| Item | Estimate |
|---|---|
Adapter crate skeleton + translate_sap_event |
1 day |
| OAuth bearer-token validation middleware | 1 day |
| HMAC signature verification | 0.5 day |
| 8 unit tests (matching the Odoo adapter coverage) | 1 day |
| Live SAP BTP sandbox integration test | 0.5–1 day |
Documentation in this file + WARP_COMPATIBLE_GUIDE.md reference |
0.5 day |
| Total (one senior Rust engineer) | 3–5 days |
Implementation follows the same pattern as the Odoo adapter shipped
in Phase 2 session 8. The SAP-specific work concentrates in two
places: the OAuth/HMAC middleware (cross-cutting, future-reusable),
and the SAP-flavored field-mapping in translate_sap_event.
Out of scope (Phase 3 work)
SalesOrder.Changed.v1— status transitions. Needs a new Warp event family becauseorder.placedonly fires on creation.BusinessPartner.Changed.v1— customer-record updates. Needs either a richerCustomerProfileor a newCustomerUpdatedevent family.- Inbound delivery events —
OutboundDelivery.Created.v1,DeliveryDocument.Posted.v1. Mapped to a futuredelivery.shippedevent family. - SAP RFC fallback — for tenants on legacy SAP without Business Eventing. Deferred until a paying customer asks; the architecture shouldn’t shape itself around a fallback nobody needs.
References
- SAP Business Eventing documentation — SAP-side specification.
- Warp adapter contract — the five rules every Warp adapter implements.
- Warp Type Spec v0.1 — the typed vocabulary the adapter emits.
- Odoo adapter (
crates/warp-catalog/src/adapters/odoo/event_bridge.rs, part of Warp’s commercial distribution, not included in this open-source repository) — the nearest reference implementation. Read this first when starting the SAP adapter — the shape and the tests transfer almost line-by-line. - ADR-0003 platform adapter interface — the architectural contract every adapter satisfies.
Owner + status tracking
- Owner: Warp core engineering (Lamar Tech).
- Status: Specification — implementation in Phase 3.
- First-target customer: to be announced once an enterprise SAP pilot signs. The implementation will happen in coordination with that pilot so the live-test data is real.
- Updates: changes to this spec require either a PR review (for field-mapping or auth changes) or an ADR (for any deviation from the five-rule adapter contract).