AutomationFlowsSocial Media › Create Branded Social Proof Instagram Posts From Airtable Reviews with…

Create Branded Social Proof Instagram Posts From Airtable Reviews with…

Original n8n title: Create Branded Social Proof Instagram Posts From Airtable Reviews with Bannerbear and Uploadtourl

ByJitesh Dugar @jiteshdugar on n8n.io

Convert your customer satisfaction into high-converting social media content with this fully automated social proof pipeline. This workflow scans your database for top-tier reviews, generates a branded quote card, and publishes it directly to Instagram, ensuring a consistent…

Cron / scheduled trigger★★★★☆ complexity22 nodesN8N Nodes UploadtourlAirtableHTTP RequestSlack
Social Media Trigger: Cron / scheduled Nodes: 22 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #14629 — we link there as the canonical source.

This workflow follows the Airtable → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "ddbe3e51-4161-460d-99a1-a6e483e0d881",
      "name": "Upload to URL",
      "type": "n8n-nodes-uploadtourl.uploadToUrl",
      "position": [
        8288,
        3424
      ],
      "parameters": {
        "binaryPropertyName": "cardImage"
      },
      "typeVersion": 1
    },
    {
      "id": "c9d602f5-fa62-4ceb-867c-c43e44731ae3",
      "name": "Template Overview1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5728,
        2384
      ],
      "parameters": {
        "width": 480,
        "height": 800,
        "content": "##  Branded Social Proof Automation\n### Bannerbear + Upload to URL  Instagram\n\n**What this does:**\nDaily schedule fetches the oldest unposted 5-star review from Airtable, generates a branded quote card via Bannerbear, uploads it via Upload to URL to get a public CDN link, then publishes to Instagram. Marks the review as posted and notifies Slack.\n\n**Flow:**\n1.  Schedule -- daily at 10 AM\n2.  Airtable -- fetch oldest unposted 5 review\n3.  IF -- skip gracefully if none found\n4.  Code -- build Bannerbear payload + IG caption\n5.  HTTP -- Bannerbear: submit image generation job\n6.  Wait 5s -- initial render buffer\n7.  HTTP -- Bannerbear: poll for completed status\n8.  IF -- completed vs still pending\n9.  HTTP -- fetch rendered image as binary\n10.  Upload to URL -- upload binary, get public CDN URL\n11.  Code -- merge CDN URL + caption fields\n12.  HTTP -- IG: create media container\n13.  Wait 6s -- IG processing buffer\n14.  HTTP -- IG: publish container\n15.  Airtable -- mark review as posted\n16.  Slack -- notify team\n\n**Required env vars:**\n'IG_USER_ID' 'IG_ACCESS_TOKEN'\n'BANNERBEAR_API_KEY' 'BANNERBEAR_TEMPLATE_ID'\n'AIRTABLE_BASE_ID' 'SLACK_CHANNEL_ID'\n\n**Airtable Reviews table fields:**\n'Reviewer Name' . 'Review Text' . 'Rating'\n'Posted' (checkbox) . 'Submitted At' (date)\n'Instagram Post ID' . 'Posted At' . 'Card Image URL'\n\n**Bannerbear template layer names:**\n'reviewer_name' . 'review_text' . 'star_label'"
      },
      "typeVersion": 1
    },
    {
      "id": "cdf0a485-7827-4883-a7ba-0941a8694c60",
      "name": "Sticky Nodes 1-",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6320,
        3200
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 380,
        "content": "###  Nodes 1-3: Schedule, Fetch & Gate\n\n**Schedule Trigger** fires daily at 10 AM.\n\n**Airtable -- Fetch Review** uses 'filterByFormula' to retrieve only the oldest unposted 5-star record. 'AND({Rating}=5, {Posted}=FALSE())' + sort by 'Submitted At asc' + 'maxRecords: 1' ensures FIFO queue dispatch.\n\n**IF -- Has Valid Review?** exits cleanly on false branch if no records are found."
      },
      "typeVersion": 1
    },
    {
      "id": "d26ae5a7-904b-4468-bf14-e4d9df09508f",
      "name": "Sticky Nodes 4-",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6960,
        3232
      ],
      "parameters": {
        "color": 7,
        "width": 408,
        "height": 340,
        "content": "###  Nodes 4-5: Build Payload & Generate Image\n\n**Code -- Prepare Payload** formats review data into Bannerbear modifications array, truncates card text to 180 chars, and builds the full Instagram caption (max 2200 chars).\n\n**HTTP -- Bannerbear Create Job** POSTs to '/v2/images'. Returns a 'uid' for polling. Generation is async -- initial response is 'pending'."
      },
      "typeVersion": 1
    },
    {
      "id": "78dc78cc-4973-435c-92b9-9fe61abb279a",
      "name": "Sticky Nodes 6-",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7408,
        3200
      ],
      "parameters": {
        "color": 7,
        "width": 500,
        "height": 380,
        "content": "###  Nodes 6-8: Poll Loop\n\n**Wait 5s** -- initial render buffer.\n\n**HTTP -- Poll Status** calls 'GET /v2/images/{uid}'. Returns 'status: pending' or 'status: completed'.\n\n**IF -- Image Ready?** branches on 'completed'. False branch exits -- extend with a loop back to Wait for full retry logic.\n\nWhen completed, 'image_url' is the rendered PNG CDN link."
      },
      "typeVersion": 1
    },
    {
      "id": "05441a5f-7a54-4e80-81d0-5b63feb34c98",
      "name": "Sticky Nodes 9-",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8032,
        3184
      ],
      "parameters": {
        "color": 7,
        "width": 540,
        "height": 420,
        "content": "###  Nodes 9-11: Fetch Binary  Upload to URL  Merge\n\n**HTTP Fetch Rendered Card** downloads the Bannerbear image as a binary ('responseFormat: file', stored in 'cardImage').\n\n**Upload to URL** -- the mandatory CDN bridge. Instagram's API requires a direct public HTTPS image URL; it rejects base64 and binary. This node uploads the binary and returns a 'public_url'.\n\n**Code -- Merge** combines the CDN URL with caption and metadata from the earlier Code node."
      },
      "typeVersion": 1
    },
    {
      "id": "b1a9d493-79ac-46d2-8c66-1ab34a00d87c",
      "name": "Sticky Nodes 12-",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8720,
        3152
      ],
      "parameters": {
        "color": 7,
        "width": 1068,
        "height": 440,
        "content": "###  Nodes 12-16: Publish, Log & Notify\n\n**IG Create Media Container** POSTs to '/media' with CDN URL + caption. Returns 'container_id'.\n\n**Wait 6s** -- IG processing buffer.\n\n**IG Publish Container** calls '/media_publish' with 'creation_id'. Returns live Post ID.\n\n**Airtable -- Mark as Posted** updates record: 'Posted=true', Post ID, timestamp, card URL. Prevents re-posting.\n\n**Slack -- Notify Team** sends reviewer name, review snippet, Post ID, and card URL."
      },
      "typeVersion": 1
    },
    {
      "id": "682ccf50-9530-4130-9c32-4265737b0f1b",
      "name": "Schedule Trigger1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        6352,
        3424
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 10
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "67376d29-bd02-478a-94e8-8e524264d847",
      "name": "Airtable Fetch Review1",
      "type": "n8n-nodes-base.airtable",
      "position": [
        6528,
        3424
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $env.AIRTABLE_BASE_ID }}"
        },
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "Reviews"
        },
        "operation": "list"
      },
      "typeVersion": 2
    },
    {
      "id": "74751a43-093e-4ec5-bad5-e51d981bcfd7",
      "name": "IF Has Valid Review1",
      "type": "n8n-nodes-base.if",
      "position": [
        6752,
        3424
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-has-review",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "c6ac9392-9df0-41c3-babf-2abb3004fbe7",
      "name": "Code Prepare Bannerbear Payload1",
      "type": "n8n-nodes-base.code",
      "position": [
        7008,
        3424
      ],
      "parameters": {
        "jsCode": "//  Prepare Bannerbear payload + IG caption \nconst review = $input.first().json;\nconst fields = review.fields || review;\n\nconst reviewerName = fields['Reviewer Name'] || 'A Happy Customer';\nconst reviewText   = fields['Review Text']   || '';\nconst rating       = Number(fields['Rating'] || 5);\nconst recordId     = review.id;\n\n// Truncate review text for card layer (180 char max)\nconst cardText = reviewText.length > 180\n  ? reviewText.substring(0, 177) + '...'\n  : reviewText;\n\n// Stars string for card layer\nconst stars = ''.repeat(Math.min(rating, 5));\n\n// Bannerbear modifications  layer names must match your template\nconst modifications = [\n  { name: 'reviewer_name', text: ' ' + reviewerName },\n  { name: 'review_text',   text: '\"' + cardText + '\"' },\n  { name: 'star_label',    text: stars },\n];\n\n// Full Instagram caption (2200 char limit)\nconst captionReview = reviewText.length > 300\n  ? reviewText.substring(0, 297) + '...'\n  : reviewText;\n\nconst hashtagBlock = '#customerreview #5stars #testimonial #socialproof #happycustomer #review #customerexperience';\n\nlet finalCaption = [\n  stars + ' Real words from a real customer.',\n  '',\n  '\"' + captionReview + '\"',\n  ' ' + reviewerName,\n  '',\n  ' See why hundreds of customers trust us.',\n  ' Link in bio.',\n  '',\n  hashtagBlock,\n].join('\\n').trim();\n\nif (finalCaption.length > 2200) {\n  finalCaption = finalCaption.substring(0, 2196) + '...';\n}\n\nconsole.log('[Payload] Reviewer:', reviewerName, '| Record:', recordId);\n\nreturn [{\n  json: {\n    record_id:              recordId,\n    reviewer_name:          reviewerName,\n    review_text:            reviewText,\n    card_text:              cardText,\n    stars:                  stars,\n    modifications:          modifications,\n    final_caption:          finalCaption,\n    ig_user_id:             $env.IG_USER_ID,\n    bannerbear_template_id: $env.BANNERBEAR_TEMPLATE_ID,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4770b12d-463e-49b1-b90f-495d66f4e105",
      "name": "HTTP Bannerbear Create Job1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        7232,
        3424
      ],
      "parameters": {
        "url": "https://api.bannerbear.com/v2/images",
        "body": "={{ JSON.stringify({ template: $json.bannerbear_template_id, modifications: $json.modifications }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "rawContentType": "application/json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.BANNERBEAR_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "861f9015-8ce5-45aa-a5cb-5c00b8502552",
      "name": "Wait Before Poll1",
      "type": "n8n-nodes-base.wait",
      "position": [
        7440,
        3424
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 5
      },
      "typeVersion": 1
    },
    {
      "id": "1cb0e82b-e0cb-4d7a-8ca2-14023323a997",
      "name": "HTTP Bannerbear Poll Status1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        7600,
        3424
      ],
      "parameters": {
        "url": "=https://api.bannerbear.com/v2/images/{{ $('HTTP Bannerbear Create Job1').first().json.uid }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.BANNERBEAR_API_KEY }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "d0edef0c-82cb-4abc-97e5-399940d387b1",
      "name": "IF Image Ready1",
      "type": "n8n-nodes-base.if",
      "position": [
        7792,
        3424
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-image-ready",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "2b1a94a8-57d9-47a9-8c30-65cc33059a66",
      "name": "HTTP Fetch Rendered Card1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        8096,
        3424
      ],
      "parameters": {
        "url": "={{ $json.image_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "cardImage"
            }
          }
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "67a627e7-1f27-4c92-82e6-d101da62b3a6",
      "name": "Code Merge Upload and Caption1",
      "type": "n8n-nodes-base.code",
      "position": [
        8448,
        3424
      ],
      "parameters": {
        "jsCode": "//  Merge CDN URL from Upload to URL + caption from Code node \nconst uploadResult = $input.first().json;\nconst prepData     = $('Code Prepare Bannerbear Payload1').first().json;\n\n// Upload to URL returns the public URL under one of these field names\nconst publicImageUrl =\n  uploadResult.public_url   ||\n  uploadResult.url          ||\n  uploadResult.file_url     ||\n  uploadResult.cdn_url      ||\n  uploadResult.imageUrl     ||\n  '';\n\nif (!publicImageUrl) {\n  throw new Error(\n    'Upload to URL returned no public URL. Available fields: ' +\n    JSON.stringify(Object.keys(uploadResult))\n  );\n}\n\nconsole.log('[Merge] CDN URL:', publicImageUrl);\n\nreturn [{\n  json: {\n    public_image_url: publicImageUrl,\n    final_caption:    prepData.final_caption,\n    ig_user_id:       prepData.ig_user_id,\n    record_id:        prepData.record_id,\n    reviewer_name:    prepData.reviewer_name,\n    review_text:      prepData.review_text,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4a29c68e-c112-4e18-b6dc-ef91dd6442b8",
      "name": "IG Create Media Container1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        8768,
        3424
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.ig_user_id }}/media",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "image_url",
              "value": "={{ $json.public_image_url }}"
            },
            {
              "name": "caption",
              "value": "={{ $json.final_caption }}"
            },
            {
              "name": "access_token",
              "value": "={{ $env.IG_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "82c6802c-24f9-4527-b60c-c6a9527bd24b",
      "name": "Wait IG Buffer1",
      "type": "n8n-nodes-base.wait",
      "position": [
        8992,
        3424
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 6
      },
      "typeVersion": 1
    },
    {
      "id": "a6ab9a7b-8e90-4ab9-be81-4d030915d784",
      "name": "IG Publish Container1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        9200,
        3424
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $('Code Merge Upload and Caption1').first().json.ig_user_id }}/media_publish",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "creation_id",
              "value": "={{ $('IG Create Media Container1').first().json.id }}"
            },
            {
              "name": "access_token",
              "value": "={{ $env.IG_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "7b4c4b8b-84af-4ddc-9631-e8ae20a401ac",
      "name": "Airtable Mark as Posted1",
      "type": "n8n-nodes-base.airtable",
      "position": [
        9424,
        3424
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $env.AIRTABLE_BASE_ID }}"
        },
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "Reviews"
        },
        "columns": {
          "value": {
            "Posted": true,
            "Posted At": "={{ new Date().toISOString() }}",
            "Card Image URL": "={{ $('Code Merge Upload and Caption1').first().json.public_image_url }}",
            "Instagram Post ID": "={{ $('IG Publish Container1').first().json.id }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {},
        "operation": "update"
      },
      "typeVersion": 2
    },
    {
      "id": "8b7e37af-9d23-4128-a245-b02757edde95",
      "name": "Slack Notify Team1",
      "type": "n8n-nodes-base.slack",
      "position": [
        9648,
        3424
      ],
      "parameters": {
        "text": "=[5-star] *Social Proof Post Published!*\n\n*Reviewer:* {{ $('Code Merge Upload and Caption1').first().json.reviewer_name }}\n*Post ID:* {{ $('IG Publish Container1').first().json.id }}\n*Posted At:* {{ new Date().toLocaleString() }}\n\n*Review:* {{ $('Code Merge Upload and Caption1').first().json.review_text.substring(0, 120) }}...\n\n*Card URL:* {{ $('Code Merge Upload and Caption1').first().json.public_image_url }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $env.SLACK_CHANNEL_ID }}"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.2
    }
  ],
  "connections": {
    "Upload to URL": {
      "main": [
        [
          {
            "node": "Code Merge Upload and Caption1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Image Ready1": {
      "main": [
        [
          {
            "node": "HTTP Fetch Rendered Card1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait IG Buffer1": {
      "main": [
        [
          {
            "node": "IG Publish Container1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger1": {
      "main": [
        [
          {
            "node": "Airtable Fetch Review1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before Poll1": {
      "main": [
        [
          {
            "node": "HTTP Bannerbear Poll Status1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Has Valid Review1": {
      "main": [
        [
          {
            "node": "Code Prepare Bannerbear Payload1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IG Publish Container1": {
      "main": [
        [
          {
            "node": "Airtable Mark as Posted1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable Fetch Review1": {
      "main": [
        [
          {
            "node": "IF Has Valid Review1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable Mark as Posted1": {
      "main": [
        [
          {
            "node": "Slack Notify Team1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Fetch Rendered Card1": {
      "main": [
        [
          {
            "node": "Upload to URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IG Create Media Container1": {
      "main": [
        [
          {
            "node": "Wait IG Buffer1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Bannerbear Create Job1": {
      "main": [
        [
          {
            "node": "Wait Before Poll1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Bannerbear Poll Status1": {
      "main": [
        [
          {
            "node": "IF Image Ready1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Merge Upload and Caption1": {
      "main": [
        [
          {
            "node": "IG Create Media Container1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Prepare Bannerbear Payload1": {
      "main": [
        [
          {
            "node": "HTTP Bannerbear Create Job1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Convert your customer satisfaction into high-converting social media content with this fully automated social proof pipeline. This workflow scans your database for top-tier reviews, generates a branded quote card, and publishes it directly to Instagram, ensuring a consistent…

Source: https://n8n.io/workflows/14629/ — original creator credit. Request a take-down →

More Social Media workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Social Media

Turn your best 5-star reviews into a daily stream of branded social proof content -- fully automated. This workflow pulls the oldest unposted 5-star review from Google Sheets, generates a custom quote

Google Sheets, HTTP Request, N8N Nodes Uploadtourl +1
Social Media

Automate your post-event Instagram carousel using a fan-out and merge pattern. One Code node splits the photos array into individual n8n items. Every photo then flows through HTTP Fetch, Upload to URL

HTTP Request, N8N Nodes Uploadtourl, Airtable +1
Social Media

This enterprise-grade n8n workflow automates the Instagram complaint handling process — from detection to resolution — using Claude AI, dynamic ticket assignment, and SLA enforcement. It converts cust

HTTP Request, Google Sheets, Slack
Social Media

Gemini - Video Analysis (NEW). Uses httpRequest, stickyNote, sort, limit. Scheduled trigger; 29 nodes.

HTTP Request, Airtable, Execute Workflow Trigger
Social Media

This enterprise-grade n8n workflow automates influencer contract compliance for Instagram campaigns — from deadline tracking to breach detection — using Claude AI, Instagram API, and smart reminders.

Google Sheets, Slack, HTTP Request