Integrations

n8n

Create and schedule posts from n8n with the HTTP Request node, and start workflows from Pozy webhooks with a verified signature.

n8n talks to Pozy through its built-in nodes: HTTP Request for actions and Webhook for triggers. This works on n8n Cloud and self-hosted n8n, with no community node to install.

You need a Pozy API key (Developers → API keys in the dashboard) and at least one connected account.

#Create the credential

#Add a Bearer credential

In n8n open Credentials → Add credential and choose Bearer Auth. Paste your pz_live_ key as the token and name it "Pozy".

#Check it

Add an HTTP Request node with:

  • Method: GET
  • URL: https://api.pozy.ai/v1/accounts
  • Authentication: Generic Credential Type → Bearer Auth → Pozy

Click Execute step. The output lists your connected accounts. Note the _id of each account you want to post to.

If your n8n version has no Bearer Auth type, use Header Auth with the name Authorization and the value Bearer pz_live_....

#Create a post

Add an HTTP Request node:

Field Value
Method POST
URL https://api.pozy.ai/v1/posts
Authentication Generic Credential Type → Bearer Auth → Pozy
Send Headers On. Name Idempotency-Key, value {{ $execution.id }}-{{ $itemIndex }}
Send Body On, Body Content Type JSON, Specify Body Using JSON

Body, with values mapped from earlier nodes:

JSON body
{
  "content": "{{ $json.text }}",
  "platforms": [
    { "platform": "linkedin", "accountId": "6650f1c2a8b4e3d2c1b0a333" }
  ],
  "mediaItems": [
    { "type": "image", "url": "{{ $json.imageUrl }}" }
  ],
  "publishNow": true
}

Drop mediaItems for a text-only post. The response contains post._id and, per platform, status and platformPostUrl once it is live.

Quotes inside mapped text

When you put {{ $json.text }} inside a JSON string, a quote or a line break in the text breaks the JSON. Map the whole value instead: "content": {{ JSON.stringify($json.text) }}, without surrounding quotes.

#Schedule for later

Replace publishNow with a time and a timezone. Compute the time in a Code node before the request:

Code node (JavaScript)
const when = new Date();
when.setDate(when.getDate() + 1);
const day = when.toISOString().slice(0, 10);

return [{ json: { ...$json, scheduledFor: `${day}T09:00:00` } }];
JSON body
{
  "content": {{ JSON.stringify($json.text) }},
  "platforms": [{ "platform": "linkedin", "accountId": "6650f1c2a8b4e3d2c1b0a333" }],
  "scheduledFor": "{{ $json.scheduledFor }}",
  "timezone": "Europe/Madrid"
}

A time without an offset is read in timezone, so this posts at 9:00 in Madrid tomorrow. See Scheduling and timezones.

#Post a file

Posts take media as URLs. For a file that only exists inside n8n (from Google Drive, an email attachment or a generated image):

  1. HTTP Request: POST https://api.pozy.ai/v1/media/presign with body { "filename": "photo.jpg", "contentType": "image/jpeg" }. Keep uploadUrl and publicUrl from the response.
  2. HTTP Request: PUT to {{ $json.uploadUrl }}, Authentication None, Body Content Type n8n Binary File, with header Content-Type: image/jpeg.
  3. Use publicUrl in mediaItems of the create-post request.

The upload URL goes straight to storage, so it must not carry your Pozy key. See Media uploads.

#Start a workflow from a Pozy event

#Add a Webhook node

Set HTTP Method to POST and Respond to Immediately. Under Options, turn on Raw Body so you can verify the signature. Copy the Production URL, then activate the workflow.

#Point Pozy at it

Create the endpoint in the dashboard under webhooks, or with one more HTTP Request node:

POST https://api.pozy.ai/v1/webhooks/settings
{
  "name": "n8n alerts",
  "url": "https://your-n8n.example.com/webhook/pozy-events",
  "events": ["post.failed", "account.disconnected"]
}

Copy the endpoint's secret from the response.

#Verify and route

Add a Code node after the Webhook node:

Code node (JavaScript)
const crypto = require("crypto");
const secret = "whsec_...";                          // the endpoint secret
const raw = await this.helpers.getBinaryDataBuffer(0, "data");
const headers = $input.first().json.headers;

const expected = crypto.createHmac("sha256", secret).update(raw).digest("hex");
const given = headers["x-pozy-signature"] ?? "";
if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
  throw new Error("Bad signature");
}

const event = JSON.parse(raw.toString("utf8"));
return [{ json: { eventId: headers["x-pozy-event-id"], ...event } }];

Then branch with a Switch node on {{ $json.event }}.

On self-hosted n8n, the Code node can only load crypto when the instance allows it: set NODE_FUNCTION_ALLOW_BUILTIN=crypto. Keep the secret in an environment variable or n8n credential rather than in the node where you can.

Pozy retries a delivery that does not get a 2xx within 5 seconds, so the same event can arrive twice. If a duplicate would hurt (an email, a Slack alert), store eventId and skip ids you have seen.

#Workflow ideas

  • Content calendar. Schedule Trigger every morning, Google Sheets reads today's rows, HTTP Request creates one post per row with Idempotency-Key set to the row id.
  • RSS to social. RSS Feed Read on your blog, IF for new items, HTTP Request creates a LinkedIn post and a shorter X post with customContent per platform.
  • Failure alerts. Webhook on post.failed, then Slack or email with post.platforms[].error.
  • Weekly report. Schedule Trigger on Mondays, GET /v1/analytics/dashboard?fromDate=...&toDate=..., then an email with the totals and top posts.

#Troubleshooting

Symptom Cause
401 with invalid_api_key The credential is not attached to the node, or the key was revoked
400 with param set The body is missing a field or a value is wrong; the error text says which
JSON parse error in the node A mapped value broke the JSON. Use JSON.stringify(...) for text values
429 with rate_limit_exceeded Too many requests in a minute. Add a Wait node in loops, or enable Batching in the node's options
Signature never matches Raw Body is off, or the secret is from a different endpoint