Capture Once, Orchestrate Everywhere: Reusable Data Capture on Passthrough Action Paths
Dynamic Orchestration
If you have ever built integrations across multiple SaaS applications, you know the trap: you end up writing custom data-extraction code for every single app. You write one script for Odoo, another for Liferay, and yet another for Mattermost. Before long, your integration layer is a tangled, unmaintainable mess of product-specific API scrapers.
At PolySaaS, we are taking a fundamentally different approach. Because our users work inside bundled apps through a passthrough architecture, the HTTP traffic is already flowing through our platform.
Instead of writing custom scrapers for every app, what if we could just capture the raw data as it flows by?
Enter our new generic capture atomics: CaptureGetResponse and CapturePostRequest. These two atomic services allow operators to capture in-flight data on any passthrough action path without writing a single line of app-specific code.
Here is how we are decoupling data capture from business logic, once and for all.
Defining the Scope: Passthrough Only
Before we dive into how these services work, it is critical to understand where they live.
These atomic services exist exclusively for one place in the PolySaaS platform: Passthrough → Action Path → Instruction → Atomic Service.
- What is In-Scope: Any passthrough action path (Odoo, Mattermost, Dolibarr, Nextcloud, Liferay, etc.), the green orchestration bar, and tenant Instructions matched on those paths.
- What is Out-of-Scope: Marketing website pages, WPForms, Stripe checkouts, or public REST APIs unrelated to passthrough.
If there is no passthrough session and no Instruction tied to an action path, these services do not run. Period.
The Two-Layer Philosophy: Capture vs. Consume
To make this architecture truly scalable, we strictly separate the act of capturing data from the act of processing it. Do not conflate the two.
- Layer 1: Capture (The focus of this post). These atomic services do only one thing: they read the GET response or POST body on a passthrough action path and publish it to a message queue (MQ). They do not transform data, and they do not “do business.”
- Layer 2: MQ Consumers. A completely separate set of atomic services acts as queue monitors. They pick up the messages, convert the payloads, and save or sync the data.
This separation is our superpower. There is absolutely no separate Python class, handler branch, or per-path code block for /odoo/contacts versus /liferay/users. Whenever you capture data, you use the exact same two atomic services. New action paths simply require new Instruction rows, not new capture code.
Meet the Services: Two Moments on the Action Path
When a subscriber uses an embedded app, operators can attach Instructions to the action path (e.g., /odoo/contacts). Depending on the direction of the traffic, we deploy one of two services:
| Atomic Service | HTTP Method | Instruction Direction | When it Captures | What it Captures |
| CaptureGetResponse | GET | RESPONSE (RES) | After the upstream app returns through passthrough. | The response body (JSON preferred) on the way out of the app. |
| CapturePostRequest | POST | REQUEST (REQ) | When the client posts through passthrough. | The outbound POST body on the way into the app. |
The Golden Mnemonic:
- GET Instruction (RES): Capture on the way out of the app.
- POST Instruction (REQ): Capture on the way into the app.
If an Instruction is set up with a mismatched method (e.g., a POST Instruction on a GET request), the service simply skips it. This allows a single action path to safely host both types of Instructions.
The Routing Engine: Topic Naming Conventions
Once the data is captured, where does it go? The atomic services publish a structured event to a RabbitMQ topic using a strict, predictable naming formula:
{Direction}.{Action_Path}.{Username}
- Direction: REQ for requests, RES for responses.
- Action Path: Sourced directly from the orchestration bar (e.g., /odoo/contacts becomes odoo.contacts).
- Username: The signed-in PolySaaS tenant (e.g., pso13).
Examples:
- RES.odoo.contacts.pso13 (A GET response captured from Odoo)
- REQ.liferay.users.pso13 (A POST request captured going into Liferay)
This formula ensures that downstream consumers can bind to topics with incredible precision—either subscribing globally by direction (RES.#), by app (REQ.odoo.#), or targeting specific users.
The Anatomy of a Capture Event
When you look at the message broker, the payload is clean, standardized, and ready for processing by Layer 2 consumers:
JSON
{
“capture”: “get_response”,
“topic”: “RES.odoo.contacts.pso13”,
“action_path”: “/odoo/contacts”,
“method”: “GET”,
“direction”: “RES”,
“response_meta”: {
“status_code”: 200,
“content_type”: “application/json”,
“source”: “upstream_response”
},
“data”: { … },
“username”: “pso13”,
“instruction_id”: 42,
“eventKey”: “odoo.contacts”
}
(Note: A max_chars parameter can be set via parameters_json to truncate massive payloads and protect the broker).
Why This Architecture Wins
- App-Agnostic: They understand standard HTTP GET/POST bodies, not proprietary SDKs.
- Instruction-Bound: Path, method, and direction are just tenant data on the action path.
- Future-Proof: Using local RabbitMQ today maps perfectly to cloud Pub/Sub tomorrow without changing the Instruction model.
- Zero Code Duplication: We reuse the exact same executescript on the next action path. A new app integration requires configuration, not code.
By leveraging PolySniffer to discover paths and applying CaptureGetResponse and CapturePostRequest via dynamic Instructions, we have created a truly “capture once, orchestrate everywhere” ecosystem. We can now observe and route traffic through Odoo, Mattermost, and Liferay using the exact same underlying mechanism.
The data is flowing. Now, it’s just up to your MQ consumers to decide what to do with it.
