{ "name": "Polar.sh → Twenty CRM Sync", "nodes": [ { "id": "webhook-trigger", "name": "Polar Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [250, 300], "webhookId": "polar-twenty-sync", "authentication": "none", "properties": { "httpMethod": "POST", "path": "polar-webhook", "responseMode": "onReceived", "responseData": "", "options": {} }, "notes": "SET ME: Configure this webhook URL in Polar.sh dashboard under Webhooks → Add endpoint. Polar sends checkout.created, checkout.updated, checkout.succeeded events." }, { "id": "code-parse", "name": "Parse Polar Payload", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [450, 300], "properties": { "language": "javaScript", "code": "// Parse Polar.sh webhook payload\n// Polar sends: type, data (containing checkout object)\n// Checkout object has: customer (email, name, billingAddress), product (name, metadata), amount, currency\n\nconst payload = $input.first().json;\nconst eventType = payload.type || '';\nconst checkout = payload.data || {};\n\n// Extract fields from Polar payload\nconst email = checkout.customer?.email || checkout.customerEmail || '';\nconst name = checkout.customer?.name || checkout.customerName || '';\nconst amount = checkout.amount || 0;\nconst currency = checkout.currency || 'usd';\nconst productName = checkout.product?.name || checkout.productName || '';\nconst metadata = checkout.product?.metadata || checkout.metadata || {};\nconst donationType = metadata.donationType || metadata.type || '';\nconst timestamp = checkout.createdAt || new Date().toISOString();\nconst polarCheckoutId = checkout.id || '';\n\n// Map donation type\nlet donationLabel = '';\nif (donationType === 'sadaqah' || productName.toLowerCase().includes('sadaqah')) {\n donationLabel = 'Sadaqah';\n} else if (donationType === 'zakat' || productName.toLowerCase().includes('zakat')) {\n donationLabel = 'Zakat';\n} else if (donationType === 'hibah' || productName.toLowerCase().includes('hibah')) {\n donationLabel = 'Hibah';\n} else {\n donationLabel = 'Donation';\n}\n\n// Format amount to display value (cents to main unit)\nconst amountDisplay = currency === 'myr' ?\n `RM${(amount / 100).toFixed(2)}` :\n `${(amount / 100).toFixed(2)} ${currency.toUpperCase()}`;\n\nreturn {\n email,\n name,\n amount,\n amountDisplay,\n currency,\n donationLabel,\n donationType,\n productName,\n polarCheckoutId,\n timestamp\n};" }, "notes": "Extracts and normalises Polar.sh webhook fields. Metadata fields are set in Polar product configuration." }, { "id": "http-twenty", "name": "Twenty CRM - Upsert Contact", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [650, 300], "credentials": { "httpRequest": { "id": "SET_ME_TwentyCRMApiKeyId", "name": "Twenty CRM API Key" } }, "properties": { "method": "POST", "url": "SET_ME_TwentyCRMAPIUrl/rest/contacts", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "bodyParameters": { "parameters": [ { "name": "email", "value": "={{ $json.email }}" }, { "name": "name", "value": "={{ $json.name }}" }, { "name": "city", "value": "" }, { "name": "phone", "value": "" }, { "name": "position", "value": "={{ $json.donationLabel }}" }, { "name": "linkedinLink", "value": "" }, { "name": "xLink", "value": "" }, { "name": "introduction", "value": "" } ] }, "options": { "timeout": 10000, "allowUnauthorizedCerts": false, "redirect": {}, "response": { "response": { "responseFormat": "json" } } }, "headers": { "parameters": [ { "name": "Content-Type", "value": "application/json" } ] } }, "notes": "SET ME: Replace the URL placeholder with your Twenty CRM instance URL (e.g., https://your-workspace.twenty.com). Configure the header auth credential with your Twenty CRM API key." }, { "id": "set-activity", "name": "Twenty CRM - Log Note Activity", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [650, 480], "credentials": { "httpRequest": { "id": "SET_ME_TwentyCRMApiKeyId", "name": "Twenty CRM API Key" } }, "properties": { "method": "POST", "url": "SET_ME_TwentyCRMAPIUrl/rest/activityTargets", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "bodyParameters": { "parameters": [ { "name": "title", "value": "={{ $json.donationLabel + ' Donation - ' + $json.amountDisplay }}" }, { "name": "body", "value": "={{ 'Donation from ' + $json.name + ' (' + $json.email + ')\\n' + 'Type: ' + $json.donationLabel + '\\n' + 'Amount: ' + $json.amountDisplay + '\\n' + 'Checkout ID: ' + $json.polarCheckoutId + '\\n' + 'Date: ' + $json.timestamp }}" }, { "name": "type", "value": "Note" }, { "name": "visibility", "value": "workspace" } ] }, "options": { "timeout": 10000, "allowUnauthorizedCerts": false, "redirect": {}, "response": { "responseFormat": "json" } }, "headers": { "parameters": [ { "name": "Content-Type", "value": "application/json" } ] } }, "notes": "SET ME: Same credential and URL base as above. This node logs a note activity for the contact." }, { "id": "noop-complete", "name": "Done", "type": "n8n-nodes-base.noOp", "typeVersion": 1, "position": [850, 300], "properties": {} } ], "connections": { "Polar Webhook": { "main": [ [ { "node": "Parse Polar Payload", "type": "main", "index": 0 } ] ] }, "Parse Polar Payload": { "main": [ [ { "node": "Twenty CRM - Upsert Contact", "type": "main", "index": 0 }, { "node": "Twenty CRM - Log Note Activity", "type": "main", "index": 0 } ] ] }, "Twenty CRM - Upsert Contact": { "main": [ [ { "node": "Done", "type": "main", "index": 0 } ] ] }, "Twenty CRM - Log Note Activity": { "main": [ [ { "node": "Done", "type": "main", "index": 0 } ] ] } }, "pinData": {}, "settings": { "executionOrder": "v1" }, "staticData": null, "tags": [], "versionId": "1.0.0" }