# Building your first Pipeline This walkthrough builds a complete, end-to-end pipeline for corporate security events — the same shape as a real "collect, trim, route to the SIEM" flow, but deliberately small and simple so you can stand it up in a few minutes. You'll ingest security events over **HTTP** (no vendor credentials or secrets to configure), drop the noisy fields your SIEM doesn't need with a simple **transform**, and route the result to a **Dev Null** output standing in for your SIEM. Because Dev Null discards everything it receives, you get a safe place to watch data flow before you wire up a live, billable destination. ``` Corp Security Events ──▶ Drop noise ──▶ SIEM (HTTP input) (Drop Key) (Dev Null output) ``` :::info[Why this shape?] This is the entry point to the cost-aware pattern security teams actually run: ingest the raw feed, trim and classify it, then send only what's worth the expensive SIEM ingest while cheaper copies go to archive and warm storage. Here we build the single hot path end-to-end; enrichment and tiered routing are natural next steps once data is flowing. ::: ## Prerequisites - A Monad organization. - An **organization API key** with the `pipeline:data:write` permission (the default **Data Sender** role is ideal for this purpose). This is the only credential you need — the HTTP input itself has no secrets. See [HTTP → Authentication](/docs/inputs/monad/monad-http#authentication). - Python 3 with the `requests` library (`pip install requests`) to drive the pipeline. - The script below could easily be converted to a shell script using curl. ## Step 1 — Create the HTTP input The [HTTP input](/docs/inputs/monad/monad-http) accepts data POSTed directly to your pipeline's ingest endpoint, so any source that can make an HTTPS request can feed it. There are no prerequisites and no secrets necessary. 1. Click on the **Inputs** sidebar nav tab 1. Click on the **Create new** button in the upper right-hand corner of the window 1. Type **`http`** into the search textbox, and scroll to the bottom of the results page to select the **Monad HTTP Input** 1. Name it **`Corp Security Events`** and click the **Create** button ## Step 2 — Add a transform to drop the fields you don't need Raw security events carry a lot of fields a SIEM never queries — internal request IDs, debug URLs, legacy event names. Dropping them before the SIEM cuts storage and license costs, and keeps the events readable. Add a [**Drop Key**](/docs/transforms/drop_key) transform named **`Drop noise`**. Drop Key removes one key per operation, so to strip the internal debug block: 1. Click on the **Transforms** sidebar nav tab 1. Click the **Create new** button in the upper right-hand corner of the window 1. Use a template for your source, since the pipeline isn't created yet, and select any of the options 1. Click the **New operation** button at the top of the Transform pane 1. Scroll to the **Drop Key** operation, and select it 1. Type into the key textbox **`debugContext`**, and then click the **Create operation** button 1. Click **Next**, give your operation a name (such as **`Drop noise`** as was mentioned above), and then click **Create** Given an event like this: ```json { "eventType": "user.authentication.sso", "actor": { "alternateId": "luis.ferreira@contoso.com" }, "outcome": { "result": "SUCCESS" }, "debugContext": { "debugData": { "requestId": "kDJFeouVsEfrSJ0", "requestUri": "/app/sso/saml" } } } ``` the transform emits the same record with `debugContext` gone. To drop more fields (for example `legacyEventType` or `transaction`), chain another Drop Key transform for each key. ## Step 3 — Add the SIEM output Add a [**Dev Null**](/docs/outputs/dev-null) output and name it **`SIEM`**. Dev Null takes no settings or secrets and discards everything sent to it — it's the standard way to prove a pipeline end-to-end without a live destination. 1. Click on the **Outputs** sidebar nav tab 1. Click the **Create new** button in the upper right-hand corner of the window 1. Type **`null`** into the search textbox, and select the **Monad /dev/null** output 1. Name it **`SIEM`**, and then click the **Create** button ## Step 4 — Wire the pipeline together and create it 1. Connect the nodes in order: **Corp Security Events → Drop noise → SIEM**. 1. Click on the **Pipelines** sidebar nav tab 1. Click the **Create new** button in the upper right-hand corner of the window 1. In the graph view, select the **`Corp Security Events`** input from the **Inputs** tab from the **Available nodes** section on the left-hand side of the pane, and drag it onto the graph 1. Switch to the **Transforms** tab, and click-and-drag the **`Drop noise`** transform onto the graph 1. Switch to the **Outputs** tab, and click-and-drag the **`SIEM`** output onto the graph 1. Click and drag from the output block on the **`Corp Security Events`** node to the input block on the **`Drop noise`** transform, and then again from the output block on the transform to the input block on the **`SIEM`** node - Note that every node's input block can only accept *ONE* incoming edge, though a node's output block can fan out to multiple edges 1. Click the **Create** button 1. A freshly enabled pipeline takes a few seconds to start. While it reports **Initializing**, ingest requests return `503` with `pipeline is not ready` — this is expected and no data is lost. Wait until the status is **Running** before sending data. See [Pipeline readiness](/docs/inputs/monad/monad-http#pipeline-readiness). ## Step 5 — Drive it with Python Every pipeline has its own ingest host, `https://.data.monad.com`. Copy your pipeline's ID from its Overview page in the Monad UI. The snippet below POSTs a small batch of corp security events — one successful SSO and one failed login — using the `data` envelope, which expands the array into one record per element. ```python import requests PIPELINE_ID = "your-pipeline-id" # from the pipeline's Overview page API_KEY = "your-org-api-key" # org key with pipeline:data:write url = f"https://{PIPELINE_ID}.data.monad.com" headers = { "Authorization": f"ApiKey {API_KEY}", "Content-Type": "application/json", } events = [ { "eventType": "user.authentication.sso", "actor": {"alternateId": "luis.ferreira@contoso.com", "displayName": "Luis Ferreira"}, "client": {"ipAddress": "198.51.100.65"}, "outcome": {"result": "SUCCESS"}, "severity": "INFO", "published": "2026-08-26T20:57:17.300Z", "debugContext": {"debugData": {"requestId": "kDJFeouVsEfrSJ0", "requestUri": "/app/sso/saml"}}, }, { "eventType": "user.session.start", "actor": {"alternateId": "priya.nair@contoso.com", "displayName": "Priya Nair"}, "client": {"ipAddress": "203.0.113.9"}, "outcome": {"result": "FAILURE", "reason": "INVALID_CREDENTIALS"}, "severity": "WARN", "published": "2026-08-26T20:58:02.110Z", "debugContext": {"debugData": {"requestId": "a1B2c3D4e5F6", "requestUri": "/login"}}, }, ] resp = requests.post(url, headers=headers, json={"data": events}) resp.raise_for_status() print(resp.json()) # -> {"status": "success", "count": 2} ``` Not using Python? The same request with `curl` — same ingest host, `Authorization: ApiKey` header, and `data` envelope: ```bash curl -X POST https://.data.monad.com \ -H "Authorization: ApiKey your-org-api-key" \ -H "Content-Type: application/json" \ -d '{ "data": [ { "eventType": "user.authentication.sso", "actor": {"alternateId": "luis.ferreira@contoso.com", "displayName": "Luis Ferreira"}, "client": {"ipAddress": "198.51.100.65"}, "outcome": {"result": "SUCCESS"}, "severity": "INFO", "published": "2026-08-26T20:57:17.300Z", "debugContext": {"debugData": {"requestId": "kDJFeouVsEfrSJ0", "requestUri": "/app/sso/saml"}} }, { "eventType": "user.session.start", "actor": {"alternateId": "priya.nair@contoso.com", "displayName": "Priya Nair"}, "client": {"ipAddress": "203.0.113.9"}, "outcome": {"result": "FAILURE", "reason": "INVALID_CREDENTIALS"}, "severity": "WARN", "published": "2026-08-26T20:58:02.110Z", "debugContext": {"debugData": {"requestId": "a1B2c3D4e5F6", "requestUri": "/login"}} } ] }' # -> {"status": "success", "count": 2} ``` Run it repeatedly to generate a steady trickle of events. The request body limit is 8 MiB per POST — split larger batches across multiple requests. ## Verify the data flowed In the pipeline view, open the **Drop noise** or **SIEM** node and use **View data** to sample records passing through it. Confirm the `debugContext` field is absent on the records leaving the transform — that's your proof the pipeline is ingesting, transforming, and routing exactly as designed. ## Next steps You now have a working ingest → transform → route pipeline. From here you can: - Add an [enrichment](/docs/enrichments/index) to resolve the actor (owner, department, risk) before you route. - Use [conditionals](/docs/conditionals/index) on the edges to send only high-signal events (failed auth, account changes, privileged logins) to the SIEM while cheaper copies go to archive — the full cost-aware, tiered pattern. - Replace the **SIEM** Dev Null node with a real [output](/docs/outputs/index) when you're ready to deliver to production.