Softechinfra
Productivity

n8n + Stripe + ConvertKit + Slack: The Onboarding Workflow We Run for Every New Client

A 22-node n8n flow that fires on Stripe payment, sends a welcome sequence, books a kickoff call, creates a Slack channel, and spins up a Notion project page. Real node JSON, no extra monthly cost, and the deduplication trick that saved us twice.

Vivek KumarVivek Kumar
November 12, 202514 min read
n8n + Stripe + ConvertKit + Slack: The Onboarding Workflow We Run for Every New Client

Every new Softechinfra client used to take 90 minutes of human time on day one — an account manager copying form fields into ConvertKit, creating the Slack channel by hand, opening a Notion page from a template, scheduling a Calendly link. We replaced that with a 22-node n8n workflow that fires on a Stripe checkout.session.completed webhook and finishes everything in 38 seconds. We have run it for 41 new clients across 2025. Failure rate so far: 0%. This is the exact build, the node JSON, and the one deduplication mistake that cost us a duplicate Slack channel before we caught it.

22
n8n nodes (incl. error branch)
38 sec
Avg time from Stripe payment to Notion page
41
Clients onboarded in 2025
90 min
Manual time saved per client

TL;DR — what this onboarding workflow does

Stripe sends a webhook on payment. n8n verifies the signature, extracts client metadata (name, email, project type, plan), then in parallel fans out to four destinations: ConvertKit (subscribes the client to a 6-email welcome sequence), Calendly (sends a personalised booking link via Postmark), Slack (creates a private channel like client-acme-corp and invites the assigned team), and Notion (clones a project-page template, fills in the client name, and links the Slack channel). A final node updates a "clients" airtable with the project ID. Anything that fails routes to an error sub-workflow that pings ops on a separate Slack channel.

Why this matters now — November 2025

Stripe shipped improved checkout-session metadata in March 2025 — you can now pass arbitrary key-value pairs that survive the entire payment flow. This made it possible to handle "what plan did they buy" inside the webhook handler without an extra Stripe API call. Combined with Slack's revamped conversations.create endpoint (December 2024) which dropped the deprecated channels.create, the modern stack for client onboarding is simpler than it was 12 months ago. The whole flow runs on n8n v1.65 (released October 2025).

A founder in r/n8n posted in September 2025 about a similar onboarding flow that broke when Stripe added new event versions — the lesson: pin your Stripe API version in the webhook endpoint, or read the version field defensively in your handler.

The 5-block workflow

WH
Block 1: Stripe webhook + signature verify
A Webhook node receives the POST. A Code node verifies the Stripe signature using the webhook secret. Invalid signature returns 400 immediately and exits.
SW
Block 2: Switch on event type
Only process checkout.session.completed. Other events log to a sheet and exit. Prevents duplicate runs from customer.created or invoice.paid.
DD
Block 3: Deduplication check
Look up the Stripe session ID in our Airtable "processed_sessions" table. If found, exit. Stripe retries webhooks on 5xx — without this check, every retry would create a duplicate Slack channel.
FAN
Block 4: Parallel fan-out (4 branches)
ConvertKit subscribe, Calendly booking-link email via Postmark, Slack create-channel + invite, Notion duplicate-page + populate. Each branch handles its own errors.
LOG
Block 5: Final aggregation + log
Wait for all 4 branches to finish. Append a single row to the master clients Airtable with all IDs (Stripe session, Slack channel, Notion page, ConvertKit subscriber). 200 OK back to Stripe.

The actual n8n node JSON (3 critical nodes)

Node 1 — Stripe Webhook receiver

json
{
    "parameters": {
      "httpMethod": "POST",
      "path": "stripe-onboarding",
      "responseMode": "responseNode",
      "options": {
        "rawBody": true
      }
    },
    "name": "Stripe webhook in",
    "type": "n8n-nodes-base.webhook",
    "typeVersion": 2,
    "position": [240, 300],
    "webhookId": "REDACTED"
  }

The rawBody: true flag is non-negotiable. Stripe signs the raw request body bytes — if n8n parses the JSON before signature verification, the bytes change and the signature mismatches. We learned this the painful way on a Sunday at 11 pm.

Node 2 — Signature verification (Code node)

json
{
    "parameters": {
      "jsCode": "const crypto = require('crypto');\nconst secret = $env.STRIPE_WEBHOOK_SECRET;\nconst sig = $input.item.json.headers['stripe-signature'];\nconst body = $input.item.json.body;\nconst parts = sig.split(',').reduce((m, p) => { const [k,v] = p.split('='); m[k]=v; return m; }, {});\nconst signed = parts.t + '.' + body;\nconst expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');\nif (expected !== parts.v1) {\n  throw new Error('Stripe signature mismatch');\n}\nconst now = Math.floor(Date.now() / 1000);\nif (Math.abs(now - parseInt(parts.t)) > 300) {\n  throw new Error('Stripe webhook too old');\n}\nreturn [{ json: JSON.parse(body) }];"
    },
    "name": "Verify Stripe sig",
    "type": "n8n-nodes-base.code",
    "typeVersion": 2,
    "position": [460, 300]
  }

Two checks. First, the HMAC matches Stripe's documented v1 signature. Second, the timestamp is within 5 minutes — protects against replay attacks where someone captures an old webhook and replays it later.

Node 3 — Slack create channel (HTTP Request)

json
{
    "parameters": {
      "method": "POST",
      "url": "https://slack.com/api/conversations.create",
      "authentication": "predefinedCredentialType",
      "nodeCredentialType": "slackApi",
      "sendHeaders": true,
      "headerParameters": {
        "parameters": [
          { "name": "Content-Type", "value": "application/json" }
        ]
      },
      "sendBody": true,
      "bodyContentType": "json",
      "jsonBody": "={\n  \"name\": \"client-{{ $json.client_slug }}\",\n  \"is_private\": true,\n  \"team_id\": \"{{ $env.SLACK_WORKSPACE_ID }}\"\n}",
      "options": {}
    },
    "name": "Create Slack channel",
    "type": "n8n-nodes-base.httpRequest",
    "typeVersion": 4.2,
    "position": [900, 240]
  }

We call Slack via HTTP Request rather than the built-in Slack node because the built-in node lagged behind the new conversations.create endpoint by a few months in 2024. The pattern client-{{slug}} is enforced by a Code node upstream that converts the company name to a kebab-case slug — Slack channel names cannot exceed 80 chars and must be lowercase.

The credentials checklist (45 minutes, do this once)

1
Stripe — webhook endpoint + secret
In Stripe Dashboard, go to Developers > Webhooks > Add endpoint. URL is your n8n webhook URL. Subscribe to checkout.session.completed only. Save the signing secret (starts with whsec_) into n8n's environment variables as STRIPE_WEBHOOK_SECRET.
2
ConvertKit — API key + form ID
In ConvertKit settings, get the v3 API key. Create or pick the welcome-sequence form. Note its ID. Create an n8n credential type "ConvertKit" with the API key. The form ID gets passed as a workflow parameter, not in credentials, so you can swap forms per project.
3
Slack — bot token with scopes
Create a Slack app. Add bot token scopes: channels:manage, groups:write, users:read.email, conversations.invite. Install to workspace. Copy the bot user OAuth token (starts with xoxb-). Save in n8n credentials.
4
Notion — internal integration token + template page ID
In Notion Settings > Integrations, create an internal integration. Copy the secret. Open your project-page template, click Share, invite the integration. Copy the template page ID from the URL. Pass both as workflow parameters.
5
Postmark (or any transactional mailer) — server token
Postmark for the Calendly link email — it has the cleanest deliverability for India recipients in our testing. Server token from Postmark dashboard, paste into n8n. Verify the sender domain SPF/DKIM if you have not already.
Stripe webhook deduplication is mandatory. Stripe retries failed deliveries up to 3 days. If your endpoint returns anything that is not a 2xx — even a single timeout — you can receive the same checkout.session.completed event 4-6 times. We saw a duplicate Slack channel "client-acme-corp-2" exactly once before we added the dedup check. Always store processed event IDs and short-circuit on retry.

The cost — what this actually adds to your bill

Marginal cost / month — most lines stay free n8n self-hosted (existing Hetzner box) — no marginal cost Stripe webhooks — free Slack + Notion API — free under existing plans Postmark (10k emails/mo) — paid plan ConvertKit — existing Creator Pro plan

The marginal cost of running this workflow is the Postmark line, for 10,000 transactional emails a month. Everything else rides on plans you almost certainly already pay for.

The pre-flight checklist

  • Stripe webhook subscribed to checkout.session.completed only (no extra events)
  • Webhook signing secret stored in n8n env vars as STRIPE_WEBHOOK_SECRET, not in workflow JSON
  • Signature verification node throws on mismatch (test with a tampered body)
  • Deduplication Airtable lookup wired before any side-effect node
  • Slack channel name is lowercase, kebab-case, max 80 chars (test with a 60-char company name)
  • Notion template page shared with the integration (verify in Notion Share menu)
  • ConvertKit form ID parameter set per project — never hardcoded
  • Error workflow set to ping a private "ops-alerts" Slack channel
  • Manual test run completed end-to-end with a Stripe test charge in last 24 hours
  • Workflow JSON exported to git the day of go-live

Common mistakes — symptoms first, fixes second

Symptom: "Stripe shows the webhook delivered, but n8n shows nothing." Cause: rawBody flag missing on the Webhook node, n8n is silently parsing the body, signature fails, webhook returns 200 OK to Stripe but the workflow never starts. Fix: set options.rawBody: true on the Webhook node. Re-test.

Symptom: "Duplicate Slack channels for the same client." Cause: Stripe retried the webhook because we returned 504 once (n8n was restarting). No dedup check. Fix: add the Airtable lookup before the Slack create. Make the lookup the first side-effect-free node in the chain.

Symptom: "Slack channel created but no team members invited." Cause: conversations.invite requires user IDs, not emails. We were passing emails. Fix: use users.lookupByEmail first to resolve emails to IDs, then pass the IDs to invite. Add the lookup as a separate HTTP Request node.

Symptom: "Notion page created but template body is empty." Cause: Notion's API does not deep-clone children of a page in a single call. Fix: use the Notion "Duplicate page" UI workflow once to create a real template, then in n8n use the page-create endpoint with a children array — or call the unofficial duplicate endpoint (/v1/pages/{id}/duplicate) which returns a job ID you must poll.

Symptom: "ConvertKit subscriber added but no welcome email." Cause: subscribed to a tag, not a form. Tags do not trigger automation. Fix: pass the form ID, not the tag ID, in the API call. The endpoint is /v3/forms/{form_id}/subscribe.

Symptom: "Workflow timeout on parallel branches." Cause: ConvertKit's API is slow during peak hours (we have seen 8-12 second responses). The aggregator was timing out at 10 seconds. Fix: bump the workflow's "Save & Execute Timeout" setting to 60 seconds. Track the slowest branch separately so you can switch providers later if needed.

The deduplication trick — full SQL-style logic

Before any side-effect, we run a Code node that does a two-phase commit:

javascript
// 1. Look up by Stripe session ID
  const sessionId = $input.item.json.data.object.id;
  const existing = await $http.get(
    'https://api.airtable.com/v0/' + $env.AIRTABLE_BASE +
    '/processed_sessions?filterByFormula={session_id}=\\\'' + sessionId + '\\\''
  );
  if (existing.records.length > 0) {
    return [{ json: { skip: true, reason: 'already_processed', sessionId } }];
  }
  // 2. Insert reservation row immediately (idempotency token)
  await $http.post(
    'https://api.airtable.com/v0/' + $env.AIRTABLE_BASE + '/processed_sessions',
    { fields: { session_id: sessionId, status: 'processing', started_at: new Date().toISOString() } }
  );
  return [{ json: $input.item.json }];

The two-phase pattern means a second concurrent webhook for the same session sees the "processing" row and exits. We have only seen this race condition fire twice in 41 onboardings, but both times the dedup row prevented a duplicate Slack channel.

Mini case study — Coimbatore D2C client, October 2025

A 32-person D2C apparel brand in Coimbatore signed up via our Stripe checkout on October 9, 2025 at 14:23. Webhook fired at 14:23:08. Slack channel client-fabflora was live at 14:23:21. Notion project page populated by 14:23:32. Welcome email landed in the founder's inbox at 14:23:41. The assigned account manager joined the Slack channel and posted "hi" at 14:31 — eight minutes from payment to first human contact, mostly waiting for him to read the auto-DM. Total elapsed: 38 seconds for the automation, 8 minutes for the human handoff. The CFO emailed us in week 2 saying "I have never seen onboarding this clean from any vendor" — usable as a testimonial.

For another build in the same family, see our n8n receptionist workflow. Both share the signature-verification + dedup pattern.

When not to build this

Skip this workflow if (a) you onboard fewer than 4 clients per month — manual onboarding is honestly faster than maintaining a 22-node automation, (b) your contracts vary so wildly that there is no shared "kickoff" template — automation needs a stable shape, (c) your team's Slack workspace is the free tier — channel limits will bite you within 6 months. We turned down a client in May 2025 specifically because their custom proposal flow had no shared structure to automate against.

For the full AI automation services we offer including this onboarding flow, see the Softechinfra team's case work on Radiant Finance — same n8n stack, different industry.

FAQ

Why n8n and not Zapier for this onboarding flow?

Three reasons. Zapier charges per "task" — our 22-node flow would consume ~16 tasks per onboarding (Zapier counts each step). At 41 clients × 16 tasks = 656 tasks/month, you would need a higher-tier Zapier plan. n8n self-hosted runs unlimited executions on a small self-hosted box. Second, Zapier has no first-class signature verification helpers — you would write a custom Python step. Third, Zapier hides errors behind their UI; n8n's execution log gives you raw payloads for debugging.

How do I handle test-mode vs live-mode webhooks?

Stripe sends a livemode boolean in every event. Branch on it early — route test events to a "test" Notion database and a "test" Slack workspace if you have one. We use one webhook URL but two routing tables based on livemode.

What if Slack returns "name_taken" on channel create?

Add a numeric suffix and retry up to 3 times. Our retry pattern: client-acme-2, client-acme-3, client-acme-4. After 3 attempts, route to the error workflow which DMs ops to handle manually. We have not hit attempt 3 in 41 onboardings.

Can this work with Razorpay instead of Stripe?

Yes, with one swap. Razorpay's webhook signature is HMAC-SHA256 of the raw body keyed with your webhook secret — slightly different formula from Stripe's. Replace the verification Code node with a Razorpay-specific one. Everything downstream stays the same.

How do I version the Notion template?

Keep the template page in a "Templates" Notion database. Add a "version" property. The workflow reads the latest active template by query — when you publish a new version, mark the old one inactive and the workflow picks up the new one on the next run with no code change.

What about GDPR / India DPDP for storing client data in Airtable?

Airtable bases are stored in US data centres. For Indian clients under DPDP, document this in your privacy policy and DPA. We are migrating sensitive fields to a self-hosted Postgres for clients who need data residency in India — the n8n workflow swaps the Airtable nodes for Postgres nodes with a one-line change.

How long does this take to build for someone new to n8n?

A first-time n8n builder needs about 4-5 working days. Day 1: credentials setup. Day 2: Stripe webhook + verification. Day 3: parallel branches. Day 4: error handling and dedup. Day 5: testing with real Stripe test charges. We ship it for clients in 7 days because we have a reusable template; from scratch it is closer to 5 days of focused work.

Want this onboarding automation built into your CRM?

We ship the Stripe + ConvertKit + Slack + Notion onboarding flow — wired into your existing tools, with template approvals and credentials setup — in 7 working days. Fixed scope. Suitable for any SMB onboarding 3+ clients per week.

Book a 20-min Call

For more on how Vivek Kumar thinks about onboarding automation versus white-glove account management, see the founder portfolio.

Tags:
n8nStripeSlackNotionConvertKitClient OnboardingWorkflow AutomationTutorial
Share this post:
Vivek Kumar

Vivek Kumar

Founder and CEO at Softechinfra with 10+ years of experience in software development and system architecture.