first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,177 @@
import type { IHookFunctions, IWebhookFunctions } from 'n8n-workflow';
import { stripeApiRequest } from '../helpers';
import { StripeTrigger } from '../StripeTrigger.node';
import { verifySignature } from '../StripeTriggerHelpers';
jest.mock('../helpers', () => ({
stripeApiRequest: jest.fn(),
}));
jest.mock('../StripeTriggerHelpers', () => ({
verifySignature: jest.fn().mockResolvedValue(true),
}));
const mockedStripeApiRequest = jest.mocked(stripeApiRequest);
const mockedVerifySignature = jest.mocked(verifySignature);
describe('Stripe Trigger Node', () => {
let node: StripeTrigger;
let mockNodeFunctions: IHookFunctions;
beforeEach(() => {
node = new StripeTrigger();
mockNodeFunctions = {
getNodeWebhookUrl: jest.fn().mockReturnValue('https://webhook.url/test'),
getWorkflow: jest.fn().mockReturnValue({ id: 'test-workflow-id' }),
getNodeParameter: jest.fn(),
getCredentials: jest.fn(),
getWorkflowStaticData: jest.fn().mockReturnValue({}),
getNode: jest.fn().mockReturnValue({ name: 'StripeTrigger' }),
getWebhookName: jest.fn().mockReturnValue('default'),
getContext: jest.fn(),
getActivationMode: jest.fn(),
getMode: jest.fn(),
getNodeExecutionData: jest.fn(),
getRestApiUrl: jest.fn(),
getTimezone: jest.fn(),
helpers: {} as any,
} as unknown as IHookFunctions;
// (mockNodeFunctions.getCredentials as jest.Mock).mockResolvedValue({
// secretKey: 'sk_test_123',
// });
mockedStripeApiRequest.mockResolvedValue({
id: 'we_test123',
secret: 'whsec_test123',
status: 'enabled',
enabled_events: ['*'],
});
mockedStripeApiRequest.mockClear();
});
afterAll(() => {
jest.clearAllMocks();
});
it('should not send API version in body if not specified', async () => {
(mockNodeFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === 'events') return ['*'];
return undefined;
});
const expectedRequestBody = {
url: 'https://webhook.url/test',
description: 'Created by n8n for workflow ID: test-workflow-id',
enabled_events: ['*'],
};
const endpoint = '/webhook_endpoints';
await node.webhookMethods.default.create.call(mockNodeFunctions);
expect(mockedStripeApiRequest).toHaveBeenCalledWith('POST', endpoint, expectedRequestBody);
const callArgs = mockedStripeApiRequest.mock.calls[0];
const requestBody = callArgs[2];
expect(requestBody).not.toHaveProperty('api_version');
});
it('should send API version in body if specified in node parameters', async () => {
(mockNodeFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === 'apiVersion') return '2025-05-28.basil';
if (param === 'events') return ['*'];
return undefined;
});
const expectedRequestBody = {
url: 'https://webhook.url/test',
description: 'Created by n8n for workflow ID: test-workflow-id',
enabled_events: ['*'],
api_version: '2025-05-28.basil',
};
const endpoint = '/webhook_endpoints';
await node.webhookMethods.default.create.call(mockNodeFunctions);
expect(mockedStripeApiRequest).toHaveBeenCalledWith('POST', endpoint, expectedRequestBody);
const callArgs = mockedStripeApiRequest.mock.calls[0];
const requestBody = callArgs[2];
expect(requestBody).toHaveProperty('api_version', '2025-05-28.basil');
});
describe('webhook signature verification', () => {
let mockWebhookFunctions: IWebhookFunctions;
const testBody = { type: 'charge.succeeded', id: 'ch_123' };
const rawBody = JSON.stringify(testBody);
beforeEach(() => {
mockWebhookFunctions = {
getBodyData: jest.fn().mockReturnValue(testBody),
getRequestObject: jest.fn().mockReturnValue({
rawBody: Buffer.from(rawBody),
body: testBody,
}),
getResponseObject: jest.fn().mockReturnValue({
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
}),
getNodeParameter: jest.fn().mockReturnValue(['*']),
helpers: {
returnJsonArray: jest.fn().mockImplementation((data) => [data]),
},
} as unknown as IWebhookFunctions;
// Reset the verifySignature mock to return true by default
mockedVerifySignature.mockResolvedValue(true);
});
it('should process webhook with valid signature', async () => {
mockedVerifySignature.mockResolvedValue(true);
const result = await node.webhook.call(mockWebhookFunctions);
expect(result).toEqual({
workflowData: [[testBody]],
});
expect(mockedVerifySignature).toHaveBeenCalledWith();
});
it('should reject webhook with invalid signature', async () => {
mockedVerifySignature.mockResolvedValue(false);
const result = await node.webhook.call(mockWebhookFunctions);
expect(result).toEqual({
noWebhookResponse: true,
});
expect(mockedVerifySignature).toHaveBeenCalledWith();
});
it('should handle events filtering correctly', async () => {
mockedVerifySignature.mockResolvedValue(true);
(mockWebhookFunctions.getNodeParameter as jest.Mock).mockReturnValue([
'payment_intent.succeeded',
]);
const result = await node.webhook.call(mockWebhookFunctions);
expect(result).toEqual({});
});
it('should process webhook when event type matches filter', async () => {
mockedVerifySignature.mockResolvedValue(true);
(mockWebhookFunctions.getNodeParameter as jest.Mock).mockReturnValue(['charge.succeeded']);
const result = await node.webhook.call(mockWebhookFunctions);
expect(result).toEqual({
workflowData: [[testBody]],
});
});
});
});
@@ -0,0 +1,234 @@
import { createHmac } from 'crypto';
import type { IWebhookFunctions } from 'n8n-workflow';
import { verifySignature } from '../StripeTriggerHelpers';
describe('StripeTriggerHelpers', () => {
describe('verifySignature', () => {
let mockWebhookFunctions: IWebhookFunctions;
const webhookSecret = 'whsec_test123456789';
const getCurrentTimestamp = () => Math.floor(Date.now() / 1000).toString();
const testBody = { type: 'charge.succeeded', id: 'ch_123' };
const rawBody = JSON.stringify(testBody);
function generateValidSignature(timestamp: string, body: string, secret: string): string {
const signedPayload = `${timestamp}.${body}`;
const signature = createHmac('sha256', secret).update(signedPayload).digest('hex');
return `t=${timestamp},v1=${signature}`;
}
beforeEach(() => {
mockWebhookFunctions = {
getCredentials: jest.fn().mockResolvedValue({
secretKey: 'sk_test_123',
signatureSecret: webhookSecret,
}),
getRequestObject: jest.fn().mockReturnValue({
header: jest.fn(),
rawBody: Buffer.from(rawBody),
}),
} as unknown as IWebhookFunctions;
});
it('should return true when no signature secret is provided', async () => {
(mockWebhookFunctions.getCredentials as jest.Mock).mockResolvedValue({
secretKey: 'sk_test_123',
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(true);
});
it('should return false when stripe-signature header is missing', async () => {
const mockHeader = jest.fn().mockReturnValue(undefined);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
expect(mockHeader).toHaveBeenCalledWith('stripe-signature');
});
it('should return false when signature format is invalid', async () => {
const mockHeader = jest.fn().mockReturnValue('invalid-format');
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return false when timestamp is missing', async () => {
const timestamp = getCurrentTimestamp();
const signature = createHmac('sha256', webhookSecret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const mockHeader = jest.fn().mockReturnValue(`v1=${signature}`);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return false when v1 signature is missing', async () => {
const timestamp = getCurrentTimestamp();
const mockHeader = jest.fn().mockReturnValue(`t=${timestamp}`);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return true when signature is valid', async () => {
const timestamp = getCurrentTimestamp();
const validSignature = generateValidSignature(timestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(true);
});
it('should return false when signature is invalid', async () => {
const timestamp = getCurrentTimestamp();
const wrongSecret = 'wrong_secret';
const invalidSignature = generateValidSignature(timestamp, rawBody, wrongSecret);
const mockHeader = jest.fn().mockReturnValue(invalidSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should handle complex signature header with multiple elements', async () => {
const timestamp = getCurrentTimestamp();
const signature = createHmac('sha256', webhookSecret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const complexHeader = `t=${timestamp},v1=${signature},v0=old_signature`;
const mockHeader = jest.fn().mockReturnValue(complexHeader);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(true);
});
it('should handle string rawBody', async () => {
const timestamp = getCurrentTimestamp();
const validSignature = generateValidSignature(timestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody, // String instead of Buffer
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(true);
});
it('should return false when rawBody is missing', async () => {
const timestamp = getCurrentTimestamp();
const validSignature = generateValidSignature(timestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: null,
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return false when signatureSecret is not a string', async () => {
const timestamp = getCurrentTimestamp();
const validSignature = generateValidSignature(timestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
(mockWebhookFunctions.getCredentials as jest.Mock).mockResolvedValue({
secretKey: 'sk_test_123',
signatureSecret: 123, // Not a string
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return false when timestamp is older than 5 minutes', async () => {
// Create timestamp that's 6 minutes (360 seconds) old
const oldTimestamp = (Math.floor(Date.now() / 1000) - 360).toString();
const validSignature = generateValidSignature(oldTimestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return false when timestamp is from the future beyond tolerance', async () => {
// Create timestamp that's 6 minutes (360 seconds) in the future
const futureTimestamp = (Math.floor(Date.now() / 1000) + 360).toString();
const validSignature = generateValidSignature(futureTimestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(false);
});
it('should return true when timestamp is within tolerance', async () => {
// Create timestamp that's 4 minutes (240 seconds) old - within 5 minute tolerance
const recentTimestamp = (Math.floor(Date.now() / 1000) - 240).toString();
const validSignature = generateValidSignature(recentTimestamp, rawBody, webhookSecret);
const mockHeader = jest.fn().mockReturnValue(validSignature);
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
header: mockHeader,
rawBody: Buffer.from(rawBody),
});
const result = await verifySignature.call(mockWebhookFunctions);
expect(result).toBe(true);
});
});
});
@@ -0,0 +1,30 @@
import * as helpers from '../helpers';
describe('adjustMetadata', () => {
it('it should adjust multiple metadata values', async () => {
const additionalFieldsValues = {
metadata: {
metadataProperties: [
{
key: 'keyA',
value: 'valueA',
},
{
key: 'keyB',
value: 'valueB',
},
],
},
};
const adjustedMetadata = helpers.adjustMetadata(additionalFieldsValues);
const expectedAdjustedMetadata = {
metadata: {
keyA: 'valueA',
keyB: 'valueB',
},
};
expect(adjustedMetadata).toStrictEqual(expectedAdjustedMetadata);
});
});
@@ -0,0 +1,97 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
const baseUrl = 'https://api.stripe.com/v1';
const meterEventResponse = {
id: 'evt_test_123',
object: 'billing.meter_event',
event_name: 'api_request',
created: 1705320600,
payload: {
stripe_customer_id: 'cus_test123',
value: 100,
},
livemode: false,
};
describe('Stripe - Meter Event Workflows', () => {
const credentials = {
stripeApi: {
secretKey: 'sk_test_fake_key',
},
};
beforeAll(() => {
// Basic meter event creation
nock(baseUrl)
.persist()
.post('/billing/meter_events', {
event_name: 'api_request',
payload: {
stripe_customer_id: 'cus_test123',
value: 100,
},
})
.reply(200, meterEventResponse);
// Meter event with identifier
nock(baseUrl)
.persist()
.post('/billing/meter_events', {
event_name: 'api_request',
identifier: 'unique_event_id_123',
payload: {
stripe_customer_id: 'cus_test123',
value: 100,
},
})
.reply(200, {
...meterEventResponse,
identifier: 'unique_event_id_123',
});
// Meter event with custom payload properties
nock(baseUrl)
.persist()
.post('/billing/meter_events', {
event_name: 'api_request',
payload: {
stripe_customer_id: 'cus_test123',
value: 100,
endpoint: '/api/v1/users',
method: 'GET',
},
})
.reply(200, {
...meterEventResponse,
payload: {
stripe_customer_id: 'cus_test123',
value: 100,
endpoint: '/api/v1/users',
method: 'GET',
},
});
// Negative value support
nock(baseUrl)
.persist()
.post('/billing/meter_events', {
event_name: 'api_request',
payload: {
stripe_customer_id: 'cus_test123',
value: -50,
},
})
.reply(200, {
...meterEventResponse,
payload: {
stripe_customer_id: 'cus_test123',
value: -50,
},
});
});
// NodeTestHarness will discover and run all workflow JSON files in this directory
new NodeTestHarness().setupTests({ credentials });
});
@@ -0,0 +1,63 @@
{
"name": "Stripe Meter Event - Basic Create",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"resource": "meterEvent",
"operation": "create",
"eventName": "api_request",
"customerId": "cus_test123",
"value": 100
},
"id": "stripe-meter-event",
"name": "Stripe",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [200, 0],
"credentials": {
"stripeApi": {
"id": "1",
"name": "Stripe API"
}
}
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Stripe",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Stripe": [
{
"json": {
"id": "evt_test_123",
"object": "billing.meter_event",
"event_name": "api_request",
"created": 1705320600,
"payload": {
"stripe_customer_id": "cus_test123",
"value": 100
},
"livemode": false
}
}
]
}
}
@@ -0,0 +1,79 @@
{
"name": "Stripe Meter Event - With Custom Payload",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"resource": "meterEvent",
"operation": "create",
"eventName": "api_request",
"customerId": "cus_test123",
"value": 100,
"additionalFields": {
"customPayload": {
"properties": [
{
"key": "endpoint",
"value": "/api/v1/users"
},
{
"key": "method",
"value": "GET"
}
]
}
}
},
"id": "stripe-meter-event",
"name": "Stripe",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [200, 0],
"credentials": {
"stripeApi": {
"id": "1",
"name": "Stripe API"
}
}
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Stripe",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Stripe": [
{
"json": {
"id": "evt_test_123",
"object": "billing.meter_event",
"event_name": "api_request",
"created": 1705320600,
"payload": {
"stripe_customer_id": "cus_test123",
"value": 100,
"endpoint": "/api/v1/users",
"method": "GET"
},
"livemode": false
}
}
]
}
}
@@ -0,0 +1,73 @@
{
"name": "Stripe Meter Event - Guard Customer ID",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"resource": "meterEvent",
"operation": "create",
"eventName": "api_request",
"customerId": "cus_test123",
"value": 100,
"additionalFields": {
"customPayload": {
"properties": [
{
"key": "stripe_customer_id",
"value": "cus_malicious_override"
}
]
}
}
},
"id": "stripe-meter-event",
"name": "Stripe",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [200, 0],
"credentials": {
"stripeApi": {
"id": "1",
"name": "Stripe API"
}
}
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Stripe",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Stripe": [
{
"json": {
"id": "evt_test_123",
"object": "billing.meter_event",
"event_name": "api_request",
"created": 1705320600,
"payload": {
"stripe_customer_id": "cus_test123",
"value": 100
},
"livemode": false
}
}
]
}
}
@@ -0,0 +1,73 @@
{
"name": "Stripe Meter Event - Guard Value",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"resource": "meterEvent",
"operation": "create",
"eventName": "api_request",
"customerId": "cus_test123",
"value": 100,
"additionalFields": {
"customPayload": {
"properties": [
{
"key": "value",
"value": "999"
}
]
}
}
},
"id": "stripe-meter-event",
"name": "Stripe",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [200, 0],
"credentials": {
"stripeApi": {
"id": "1",
"name": "Stripe API"
}
}
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Stripe",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Stripe": [
{
"json": {
"id": "evt_test_123",
"object": "billing.meter_event",
"event_name": "api_request",
"created": 1705320600,
"payload": {
"stripe_customer_id": "cus_test123",
"value": 100
},
"livemode": false
}
}
]
}
}
@@ -0,0 +1,67 @@
{
"name": "Stripe Meter Event - With Identifier",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"resource": "meterEvent",
"operation": "create",
"eventName": "api_request",
"customerId": "cus_test123",
"value": 100,
"additionalFields": {
"identifier": "unique_event_id_123"
}
},
"id": "stripe-meter-event",
"name": "Stripe",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [200, 0],
"credentials": {
"stripeApi": {
"id": "1",
"name": "Stripe API"
}
}
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Stripe",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Stripe": [
{
"json": {
"id": "evt_test_123",
"object": "billing.meter_event",
"event_name": "api_request",
"created": 1705320600,
"identifier": "unique_event_id_123",
"payload": {
"stripe_customer_id": "cus_test123",
"value": 100
},
"livemode": false
}
}
]
}
}
@@ -0,0 +1,63 @@
{
"name": "Stripe Meter Event - Negative Value",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"resource": "meterEvent",
"operation": "create",
"eventName": "api_request",
"customerId": "cus_test123",
"value": -50
},
"id": "stripe-meter-event",
"name": "Stripe",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [200, 0],
"credentials": {
"stripeApi": {
"id": "1",
"name": "Stripe API"
}
}
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Stripe",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Stripe": [
{
"json": {
"id": "evt_test_123",
"object": "billing.meter_event",
"event_name": "api_request",
"created": 1705320600,
"payload": {
"stripe_customer_id": "cus_test123",
"value": -50
},
"livemode": false
}
}
]
}
}