com.sap.cds:cds-feature-n8n
Triggers n8n workflows from a CAP Java application through @n8n.process.start annotations or a programmatic N8nService, delivered by webhook calls through the CAP outbox.
Fires an HTTP request at an n8n webhook when a CDS event happens: annotate an entity or action with @n8n.process.start and the plugin fires the request when the annotated event occurs, posting the entity's fields as JSON, or call N8nService.trigger() from a handler for full control over payload and method. The webhook base URL is set directly in application.yaml, resolved from a BTP destination through the SAP Cloud SDK for production deployments behind a proxy, or left to a development-only localhost fallback that throws at startup in any other profile. Delivery runs through the CAP persistent outbox with configurable retry, retrying only network-level failures; a console mode logs calls instead of sending them, for local development and CI environments without a running n8n instance. It is the CAP Java counterpart to @cap-js/n8n@cap-js/n8nTriggers n8n workflows from a CAP application through @n8n.process.start annotations or a programmatic n8n service, resolving credentials from local config, a Cloud Foundry binding or a BTP destination.sap official, cap; the Maven coordinate is com.sap.cds:cds-feature-n8n, at 0.0.1.
Facts
Where it sits
Who is behind it
In the atlas: maintains 10@cap-javamaintainerHristina IvanovaContributes to this
In the atlas: contributes to 1@ivanovahrLisa Julia NebelContributes to this
In the atlas: contributes to 15@lisajuliaSamyuktha PrabhuContributes to this
In the atlas: contributes to 4@samyuktaprabhuSAP Open Source Admin BotContributes to this
In the atlas: contributes to 2@SAP-OSPO-ADMIN4 contributors
What it declares
Every artifact
- Type
- Package
- Origin
- By SAP
- Runtime
- Java
As a package
- Where it runs
- Inside its host
- Distributed through
- Maven
Connections
Driving an external workflow engine and reacting to what it emits
Named, credentialed addresses of other systems resolved at runtime
Download
Where it sits in your app
Download
README
quoted from cap-java/cds-feature-n8n under Apache-2.0, readSAP Cloud Application Programming Model, n8n plugin for Java
CAP Plugin to automatically trigger and interact with n8n workflow automation tool - declaratively via @n8n.process.start annotations and programmatically via the
n8n service.
Table of Contents
- About this project
- Requirements
- Quick Start
- Connect to n8n
- Webhook Requests
- Annotations
- Programmatic API
- Delivery Behaviour
- Tests
- Support, Feedback, Contributing
- Security / Disclosure
- Code of Conduct
- Licensing
About this project
cds-feature-n8n is a Spring Boot auto-configuration plugin for CAP Java applications. It listens to CDS events (CREATE, DELETE, and custom actions) annotated with @n8n.process.start and fires HTTP requests to configured n8n webhook URLs — enabling you to trigger n8n workflows directly from your CAP service layer.
Features:
- Annotation-driven: no boilerplate code needed in your service handlers
- Supports entity CRUD events (CREATE, DELETE) and custom actions/functions
- Configurable HTTP method per trigger (
GET,POST,PUT,PATCH,DELETE,HEAD) — defaults toPOST - Reliable delivery via CAP persistent outbox with configurable retry
- Optional API key header (
X-N8N-API-KEY) for authentication
Requirements
- Java 21+
- CAP Java 5+
- Spring Boot 4+
- A running n8n instance
Quick Start
Follow these steps to get a fully working local environment from scratch.
- Build and install the plugin locally:
mvn clean install
- Add the dependency to your CAP Java application's
pom.xml:
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-n8n</artifactId>
<version>0.0.1</version>
</dependency>
- Annotate an entity in your CDS model:
annotate AdminService.Books with @n8n.process.start: [
{on: 'DELETE', path: 'book-deleted'}
];
- Configure the n8n base URL in
application.yaml:
n8n:
base-url: ${N8N_BASE_URL:http://localhost:5678}
api-key: ${N8N_API_KEY:}
- Start the sample app
cd samples/bookshop/srv
mvn spring-boot:run
The app starts on http://localhost:8080 and points at http://localhost:5678/webhook by default. Any annotated CDS event will fire a webhook to your local n8n instance.
-
Smoke test
- In n8n, create a workflow with a Webhook node, path
book-deleted, save (Cmd+S), click "Listen for Test Event" - Delete any book at
http://localhost:8080→ Admin → Books - n8n should show a green execution with
{ "ID": "...", "title": "...", "author_ID": "..." }
- In n8n, create a workflow with a Webhook node, path
Alternative (test mode): If you prefer one-shot manual testing, set
n8n.use-test-webhook: trueinapplication.yaml, restart the app, then click "Listen for Test Event" in n8n instead of activating the workflow.
Connect to n8n
Local n8n
docker volume create n8n_data
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-e GENERIC_TIMEZONE="Europe/Berlin" \
-e TZ="Europe/Berlin" \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_RUNNERS_ENABLED=true \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n
Replace Europe/Berlin with your local timezone (e.g. America/New_York). The named volume n8n_data persists your workflows across container restarts. n8n will be available at http://localhost:5678.
First run only: open
http://localhost:5678in a browser and create an owner account before proceeding.
Environment Variables
Configure the n8n base URL and optional API key in application.yaml:
n8n:
base-url: ${N8N_BASE_URL:http://localhost:5678}
api-key: ${N8N_API_KEY:}
N8N_API_KEY is sent as X-N8N-API-KEY on every webhook request — this is the same header n8n uses for its public REST API.
Set N8N_API_KEY in your environment (or ~/.zshrc) and configure the n8n Webhook node with Authentication: Header Auth, Name: X-N8N-API-KEY, Value: same string.
Without it, n8n must have Authentication: None — otherwise it returns 403.
BTP Destination
For production deployments — especially SAP managed n8n instances behind a proxy — you can configure a BTP destination instead of a plain base URL. The destination takes priority over base-url:
n8n:
destination: my-n8n-dest # BTP destination name (takes priority over base-url)
api-key: ${N8N_API_KEY:} # optional; sent as X-N8N-API-KEY in addition to any proxy auth
When destination is set, the plugin resolves it via the SAP Cloud SDK at startup. The destination's URL and auth headers (e.g. Authorization: Bearer … for OAuth2 destinations) are merged into every request. X-N8N-API-KEY is then added on top — so both the outer proxy auth and the n8n-level API key are sent.
The destination can also carry the API key as a custom property (URL.headers.X-N8N-API-KEY) instead of setting n8n.api-key — though n8n.api-key takes precedence if both are set.
To use destinations, add cloudplatform-connectivity to your application's dependencies:
<dependency>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>cloudplatform-connectivity</artifactId>
</dependency>
Credential Resolution
The plugin resolves connection details in this order:
n8n.use-console: true— console mode, takes precedence over all other configuration (see Console Mode)n8n.destination— BTP destination (takes priority over base-url)n8n.base-url— explicit base URL in application.yaml- Dev-only fallback:
http://localhost:5678(development profile only — throws at startup in any other profile)
n8n.api-key is independent of the above and is always sent as X-N8N-API-KEY when set.
Webhook Requests
Test Webhooks
The path value in each annotation is appended after /webhook to form the full webhook URL:
path: 'book-deleted' → http://localhost:5678/webhook/book-deleted
Test vs. production webhooks
| Mode | n8n URL | When to use |
|---|---|---|
| Production (default) | /webhook | Workflows are active and handle every call |
| Test | /webhook-test | One-off manual testing; requires clicking "Listen for Test Event" in the n8n UI each time, and cannot handle bulk calls |
Toggle test mode via use-test-webhook in application.yaml:
n8n:
use-test-webhook: true # set to false (default) for production webhooks
Annotations
Annotate entities or actions in your CDS model with @n8n.process.start. No additional Java code is needed — the plugin detects the annotation and fires the webhook automatically.
Triggering a workflow
Each trigger entry supports the following properties:
| Property | Required | Description |
|---|---|---|
on | yes | Event name — CREATE, READ, UPDATE, DELETE, or the action name |
path | yes | Appended to n8n.base-url + /webhook (or /webhook-test if use-test-webhook: true in application.yaml) to form the full webhook URL |
method | no | HTTP method to use when calling the n8n webhook — one of GET, POST, PUT, PATCH, DELETE, HEAD. Defaults to POST. Must match the method configured on the n8n Webhook node. |
inputs | no | Fields to include in the payload; defaults to all direct entity attributes when omitted |
Entity events (CRUD):
annotate AdminService.Books with @n8n.process.start: [
{on: 'DELETE', path: 'book-deleted', inputs: [$self.ID, $self.title, $self.stock]},
{on: 'UPDATE', path: 'book-updated', method: 'PUT', inputs: [$self.ID, $self.title]}
];
An invalid value (e.g. method: 'YOLO') causes the application to fail at startup with a descriptive error:
IllegalStateException: @n8n.process.start[0] on entity 'MyService.Books' has invalid 'method' value 'YOLO'.
Allowed values: GET, POST, PUT, PATCH, DELETE, HEAD.
Note:
GETsends no request body — the payload is serialized as query parameters instead. All other methods send a JSON body.
For custom actions:
annotate CatalogService.submitOrder with @n8n.process.start.on: 'submitOrder'
@n8n.process.start.path: 'order-submitted';
When the annotated event fires, the plugin posts the selected inputs fields as a flat JSON object to the configured webhook URL. For example, with the inputs list above:
{
"ID": "abc123",
"title": "The Hobbit",
"stock": 42
}
When inputs is omitted, all direct fields of the entity are included in the payload. Specify inputs explicitly to limit which fields are sent — useful to avoid exposing sensitive or large fields.
Association fields can be included using dot notation — the plugin issues a single expanded query to fetch the associated data:
annotate AdminService.Books with @n8n.process.start: [
{on: 'DELETE', path: 'book-deleted', inputs: [$self.ID, $self.title, $self.author.name]}
];
This produces a payload with the leaf field name as the key:
{
"ID": "abc123",
"title": "The Hobbit",
"name": "Tolkien"
}
Note:
- Only one level of association traversal is supported (
$self.author.name). Deeper paths ($self.author.address.city) are skipped with a warning. This is a known limitation compared to the Node.js plugin — contributions welcome.- Association paths are not resolved for
CREATEevents. For these, the plugin uses the raw request payload (the data as submitted), so association fields like$self.author.namewill benull. Use direct FK fields (e.g.$self.author_ID) forCREATEtriggers instead.
Conditions
Add an if expression to a trigger entry to fire the webhook only when the condition is met:
annotate AdminService.Books with @n8n.process.start: [
{on: 'DELETE', path: 'book-deleted', if: (stock = 0), inputs: [$self.ID, $self.title, $self.author_ID]},
{on: 'UPDATE', path: 'book-updated', inputs: [$self.ID, $self.title]},
{on: 'UPDATE', path: 'book-low-stock', inputs: [$self.ID, $self.title, $self.stock],
if: (stock < 10)}
];
Only deletes where stock is 0 fire book-deleted; every update fires book-updated; only updates that bring stock below 10 also fire book-low-stock — e.g. to trigger a reorder workflow in n8n.
The if property is optional. When present, the webhook fires only when the condition evaluates to true against the entity row:
| Property | Required | Description |
|---|---|---|
if | no | CDS expression — webhook fires only when the condition is true |
Supported operators: =, ==, !=, <>, <, <=, >, >=, in, like, between, is null, is not null, not, and, or.
Note: The
ifcondition is evaluated in application code against the entity row available at the time the event fires — it is not pushed to the database. This means it uses the same data the plugin already has: the CQN payload for CREATE, and the prefetched row for UPDATE and DELETE. Complex expressions involving subqueries or navigation paths that aren't part of the prefetched columns will not work.
Multiple Triggers
Multiple trigger entries for the same event on the same entity are supported — all matching entries fire. This allows you to route to different n8n workflows from one event, optionally with different if conditions.
Programmatic API
Trigger a Workflow
For cases where you need full control over when and what is sent, inject N8nService directly into any CAP event handler and call .trigger():
@Component
@ServiceName(AdminService_.CDS_NAME)
public class AdminServiceHandler implements EventHandler {
@Autowired
private N8nService n8nService;
@After(event = CqnService.EVENT_CREATE, entity = "AdminService.Books")
public void afterCreateBook(List<Books> books) {
books.forEach(book -> n8nService.trigger("book-created", Map.of(
"ID", book.getId(),
"title", book.getTitle()
))); // defaults to POST when no HTTP Method is specified
}
}
The first argument to .trigger() is the webhook path — appended after /webhook to form the full URL (e.g. "book-created" → http://localhost:5678/webhook/book-created). The second argument is the payload — any Map<String, Object> you choose to send. The third argument is the HTTP method — pass POST, PUT, PATCH, DELETE, GET, or HEAD. It must match the method configured on the n8n Webhook node. If you omit the third argument, POST is used by default.
Note: The annotation-based and programmatic approaches are independent. You can use both in the same application, but take care not to fire duplicate webhooks for the same event.
Delivery Behaviour
The plugin retries failed webhook calls only on network-level errors — when n8n is unreachable (connection refused, timeout). HTTP error responses are not retried:
| Response | Meaning | Retried? |
|---|---|---|
| Network error / timeout | n8n is down or unreachable | Yes |
| 5xx | n8n responded but the workflow itself failed | No |
| 4xx | Misconfiguration (wrong URL, bad auth) | No |
Retry behavior is managed by the CAP persistent outbox. Configure it under cds.outbox.services.N8nOutbox in your application.yaml:
cds:
outbox:
services:
N8nOutbox:
maxAttempts: 10 # total attempts before the message is marked as failed
ordered: true # process messages in submission order (default: true)
Console Mode
The plugin ships a built-in console mode for local development and CI environments where no n8n instance is available. When enabled, webhook calls are logged instead of sent via the specified HTTP Method — the app behaves normally but never makes an HTTP request to n8n. Delivery is synchronous and skips the persistent outbox entirely, so no rows are written to cds_outbox_Messages.
n8n:
use-console: true
No base-url is needed. Console mode takes precedence over all other configuration. You'll see:
INFO ConsoleN8NWebhookService - [console-n8n-service]: would POST /webhook/book-deleted
Inject ConsoleN8NWebhookService to assert on webhook calls without a real n8n instance:
@SpringBootTest
@TestPropertySource(properties = "n8n.use-console=true")
class MyServiceTest {
@Autowired
ConsoleN8NWebhookService consoleWebhookService;
@Test
void deleteBook_triggersWebhook() {
// ... trigger a delete ...
assertThat(consoleWebhookService.getExecutions()).hasSize(1);
Map<String, Object> exec = consoleWebhookService.getExecutions().get(0);
assertThat(exec.get("path")).isEqualTo("book-deleted");
assertThat(exec.get("status")).isEqualTo("success");
}
}
Each execution record contains: id, path, method, payload, startedAt, finishedAt, status.
If n8n.use-console is false (the default) and n8n.base-url is not set:
| Profile | Behaviour |
|---|---|
development | Warns at startup and falls back to http://localhost:5678. HTTP calls fail gracefully if n8n is not running — the outbox retries with backoff. |
| any other | Throws IllegalStateException at startup: set N8N_BASE_URL or use n8n.use-console=true. |
Tests
Run the unit tests from the project root:
mvn test
To also generate a JaCoCo coverage report (unit tests only):
mvn verify
To include the retry integration test in coverage:
mvn verify -pl cds-feature-n8n -am -Dtest="N8nHandlerTest,N8nWebhookServiceRetryIT"
The report is written to cds-feature-n8n/target/site/jacoco/index.html.
Integration tests
The retry integration test (N8nWebhookServiceRetryIT) is excluded from the default mvn test run because Maven Surefire skips *IT.java classes by default. Run it explicitly:
mvn test -pl cds-feature-n8n -am -Dtest=N8nWebhookServiceRetryIT
This test uses WireMock to start a local HTTP server that stands in for n8n, verifying that the retry logic fires the webhook up to three times before giving up.
Testing with the sample application
The samples/bookshop directory contains a complete CAP bookshop app that demonstrates the plugin with real webhook triggers.
For a full walkthrough including starting n8n locally in Docker, see Local n8n.
Quick start (assumes n8n is already running):
cd samples/bookshop/srv
mvn spring-boot:run
The sample configures three webhook triggers on AdminService.Books: DELETE with if: (stock = 0) fires book-deleted; every UPDATE fires book-updated; and updates that bring stock below 10 also fire book-low-stock. See Local n8n for the full walkthrough. 404 → listener expired or workflow not saved. 403 → X-N8N-API-KEY mismatch.
Support, Feedback, Contributing
This project is open to feature requests/suggestions, bug reports etc. via GitHub issues. Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our Contribution Guidelines.
Security / Disclosure
If you find any bug that may be a security problem, please follow our instructions at in our security policy on how to report it. Please do not create GitHub issues for security-related doubts or problems.
Code of Conduct
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its Code of Conduct at all times.
Licensing
Copyright 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors. Please see our LICENSE for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available via the REUSE tool.
History
Changed 3
Notes
Changed
- Webhook authentication is now configurable via
n8n.webhook-auth.*— supportsbasic(username + password),header(custom header name and value), andbearer(token) auth types, matching the three auth options of the n8n Webhook node X-N8N-API-KEYis no longer forwarded to webhook nodes; it is reserved for future n8n REST API calls (/api/v1/…)- BTP destination path:
X-N8N-API-KEYis filtered from destination headers before forwarding to webhook nodes;n8n.webhook-authis merged on top
Features 12
Notes
Added
- Annotation-driven n8n webhook triggers via
@n8n.process.start— no boilerplate service handler code required - Supports CDS entity events (
CREATE,READ,UPDATE,DELETE) and custom actions/functions as trigger sources - Configurable HTTP method per trigger (
GET,POST,PUT,PATCH,DELETE,HEAD) via themethodproperty — defaults toPOST - Reliable webhook delivery via CAP persistent outbox with configurable retry
- Webhook authentication via
X-N8N-API-KEYheader, configured throughn8n.api-key/N8N_API_KEY - BTP destination support: configure
n8n.destinationto resolve the n8n base URL and authentication headers from the BTP Destination Service — no redeployment needed when the n8n instance changes - Optional
inputspayload selection on@n8n.process.start; when omitted, all direct entity fields are sent automatically - Association path inputs (e.g.
$self.category.name) for including resolved to-one association fields in the webhook payload ConsoleN8NWebhookService: offline/test mode that logs webhook calls instead of making HTTP requests — enable withn8n.use-console=true- Profile-aware startup validation: warns and falls back to
http://localhost:5678in thedevelopmentprofile; fails fast in all other profiles whenn8n.base-urlis not set n8n.base-urlexpects the bare n8n host (e.g.http://localhost:5678) — the plugin appends/webhookautomatically, or/webhook-testwhenn8n.use-test-webhook=true- Programmatic API via the
n8nservice for triggering workflows from custom handler code
- Annotation-driven n8n webhook triggers via
Others providing
Compare all 4 on the Workflow integration page →- @cap-js/n8n@cap-js/n8nTriggers n8n workflows from a CAP application through @n8n.process.start annotations or a programmatic n8n service, resolving credentials from local config, a Cloud Foundry binding or a BTP destination.sap official, capTriggers n8n workflows from a CAP applicationfrom CAP
- @cap-js/process@cap-js/processStarts, cancels, suspends and resumes SAP Build Process Automation processes from a CAP service through entity annotations, or through a typed CDS service imported from the process definition.sap official, capStarts and manages SAP Build Process Automation processes from CAPfrom CAP
- cap-n8ncap-n8nMaps CAP onto n8n: a programmatic workflow API, @n8n.trigger annotations dispatched through the transactional outbox, and a typed cds import --from n8n.independent, capIntegration of CAP with n8n workflows and triggersfrom CAP
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
Flavors
“cds-feature-n8n is a Spring Boot auto-configuration plugin for CAP Java applications.” — cap-java/cds-feature-n8n — README
History in the atlas
- Fires an HTTP request at an n8n webhook when a CDS event or action annotated with @n8n.process.start occurs, or through a programmatic N8nService.trigger() call, with delivery through the CAP persistent outbox and retry limited to network-level failures.
- First seen.
For your agent
- Ref
artifact:cap-java-cds-feature-n8n- JSON
- /artifacts/cap-java-cds-feature-n8n.json
- MCP
- Connecting an agent