How to Sync Stripe Subscription Payments to a Monday Revenue Board

Build a Spojit workflow that runs every morning, pulls the previous day of Stripe charges and invoices, has an Agent-mode Connector node summarize them into clean revenue rows, writes those rows to a Monday.com board, and posts a recap to Slack.

What This Integration Does

Finance and operations teams usually find out about subscription revenue by logging into Stripe and reading raw line items, then re-keying the numbers into a planning board by hand. This workflow removes that step entirely. On a daily schedule it reads the previous day of paid activity from your Stripe account, asks an Agent-mode Connector node to roll those payments up into a tidy per-customer revenue summary, and lands each summary as an item on a Monday.com board you already use for revenue tracking. A short Slack recap tells the team the day is reconciled without anyone opening Stripe.

The workflow is started by a Schedule trigger (a 5-field cron expression plus an IANA timezone), so there is no manual button to press. Each run reads Stripe with list-charges and list-invoices, passes that data through a Connector node in Agent mode that returns structured JSON, loops over the summarized rows to create or update Monday items, and finishes with one Slack message. Runs are stateless: each morning re-reads the prior day from Stripe rather than tracking a cursor, so a re-run of the same day is safe as long as you key Monday items on a unique value (covered in Step 4) so a repeat does not create duplicate rows.

Prerequisites

  • A Stripe connection in Spojit (added under Connections - Add connection) with read access to charges and invoices. Use a restricted key scoped to read-only where possible.
  • A Monday.com connection with permission to read and write items on the target board.
  • A Slack connection with permission to post to your finance channel.
  • A Monday board ready for revenue, plus its boardId and the column IDs you want to populate (date, amount, customer, status). Note: Monday column IDs are internal keys like numbers or text8, not the column titles you see in the UI.
  • The currency your Stripe account settles in, so you can divide amounts correctly (Stripe reports amounts in the smallest currency unit, for example cents).

Step 1: Start the workflow on a daily schedule

Add a Trigger node and set its type to Schedule. Enter a 5-field Unix cron expression and an IANA timezone so the run fires once each morning after the prior day has fully closed. For example, to run at 7:00 AM Sydney time on every day:

Cron:     0 7 * * *
Timezone: Australia/Sydney

The trigger output is { scheduledAt }. To compute the date window for "yesterday", add a Connector node in Direct mode using the built-in date connector. Call subtract to roll {{ trigger.scheduledAt }} back one day, then start-of and end-of (unit day) to get the day boundaries. Store the result as window so later steps can reference {{ window.start }} and {{ window.end }}.

Step 2: Pull the previous day of Stripe payments

Add a Connector node on the Stripe connector in Direct mode and choose the list-charges tool. Set limit to 100 (the maximum per page). Leave customer empty to read all customers. Store the result as charges.

Add a second Stripe Direct-mode node calling list-invoices, set status to paid and limit to 100, and store it as invoices. Filtering to paid here keeps drafts and voided invoices out of your revenue numbers. Each list returns a page of records plus a cursor; if you expect more than 100 records in a day, see the pagination note in Common Pitfalls.

list-charges  -> limit: 100
list-invoices -> status: "paid", limit: 100

Step 3: Summarize the payments with an Agent-mode node (structured output)

Add a Connector node in Agent mode so the agent can read both Stripe lists and produce one clean row per customer. In the prompt, hand it the data and explain the date window:

Summarize these Stripe payments for {{ window.start }}.
Charges: {{ charges }}
Paid invoices: {{ invoices }}
Group by customer. Convert amounts from the smallest currency
unit to whole units. Return one row per customer.

Turn on the Response Schema so the node returns predictable JSON instead of prose. This is the structured-output feature: every run yields the same shape, which is what makes the Monday loop in the next step reliable. Use a schema like:

{
  "type": "object",
  "properties": {
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "customerId": { "type": "string" },
          "customerName": { "type": "string" },
          "amount": { "type": "number" },
          "currency": { "type": "string" },
          "paymentCount": { "type": "integer" }
        },
        "required": ["customerId", "amount", "currency"]
      }
    }
  },
  "required": ["rows"]
}

Store the result as summary. You will iterate over {{ summary.rows }} next.

Step 4: Write each customer row to the Monday revenue board

Add a Loop node in ForEach mode over {{ summary.rows }} so each customer becomes one item. Name the loop variable row.

To avoid duplicate rows when a day is re-run, first add a Connector node on the Monday.com connector in Direct mode calling list-items on your boardId, and use a Condition node to check whether an item already exists for {{ row.customerId }} on {{ window.start }}.

On the false branch (no existing item), add a Monday.com Direct-mode node calling create-item. Set boardId to your board, name to a readable label, and map columnValues as an object keyed by your column IDs:

create-item
  boardId: "1234567890"
  name: "{{ row.customerName }} - {{ window.start }}"
  columnValues:
    date4:    { "date": "{{ window.start }}" }
    numbers:  "{{ row.amount }}"
    text8:    "{{ row.currency }}"
    status:   { "label": "Reconciled" }

On the true branch (item exists), call update-item instead with that item's itemId and the same columnValues object, so a re-run refreshes the figures rather than adding a second row. Replace the column IDs above with the real IDs from your board.

Step 5: Post a Slack recap of the day's revenue

After the loop completes, add a Connector node on the Slack connector in Direct mode and choose send-message. Set the channel to your finance channel and template the text from the summary so the team gets the headline without opening the board:

send-message
  channel: "#revenue"
  text: "Stripe revenue synced for {{ window.start }}: {{ summary.rows.length }} customers updated on the Monday board."

If you would rather email the recap, you can use a Send Email node instead; it sends from Spojit's built-in mail service with no extra connection. To send from your own domain, use the Resend or SMTP connector.

Step 6: Save, name, and enable the workflow

Give the workflow a clear name like "Daily Stripe to Monday revenue sync", save it, then enable it so the schedule starts firing. You can scaffold the whole structure quickly by asking Miraxa: "Build a workflow that runs daily, lists Stripe charges and paid invoices, summarizes them per customer with a Response Schema, creates or updates items on my Monday board, and posts a Slack recap." Then fine-tune each node's fields in the properties panel.

Tips

  • Stripe amounts are in the smallest currency unit. Tell the Agent-mode node to divide by 100 for two-decimal currencies (and remember some currencies have no decimal places).
  • Keep list-charges and list-invoices filtered tightly to one day so the Agent-mode node receives a small, cheap payload and returns faster.
  • Map Monday columnValues using the column IDs (for example numbers, text8), not the visible column titles. Open the board, fetch it with get-board, and read the IDs once.
  • Run the schedule late enough in the morning that the previous day is fully closed in your account timezone, otherwise the last payments of the day can be missed.

Common Pitfalls

  • Pagination: list-charges and list-invoices return at most 100 records per call and a cursor. If a busy day exceeds 100, wrap the list call in a Loop (While mode) that passes starting_after until the page returns fewer than 100 records.
  • Duplicate rows on re-run: the workflow re-reads the same day if it runs twice, so always check for an existing item with list-items and use update-item on a match. Skipping the Condition node creates duplicate revenue rows.
  • Timezone mismatch: the Schedule timezone, your date-window math, and Stripe's account timezone must agree, or "yesterday" will be off by a day at the boundaries.
  • Free-text from the agent: if you skip the Response Schema, the Agent-mode node may return prose that the Monday loop cannot read. The structured output schema in Step 3 is what keeps the rows machine-readable.

Testing

Before enabling the schedule, validate on a small scope. Temporarily lower the Stripe limit to a handful of records, or filter list-charges by a single test customer ID, and run the workflow once. Confirm the Agent-mode node returns the expected summary.rows shape, that one Monday item is created with the right column values, and that the Slack recap reads correctly. Re-run the same window a second time and verify the Condition branch updates the existing item instead of creating a duplicate. When the dry run looks right, restore limit: 100, remove any test filters, and enable the workflow.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.