Handleidingen · n8n

Automatisch vacatures schrijven en plaatsen met n8n, Claude en LinkedIn API

Vacatureteksten genereren en op LinkedIn plaatsen met n8n, Claude en de LinkedIn API. Minder HR-werk, sneller live.

🟡 Gevorderd — beetje technisch ⏱ 75 minuten
Tools: n8nClaudeLinkedIn APINotionGmail
Goud-op-zwart illustratie: een personeelspas verbonden met rooster, vacature, checklist en team

Een nieuwe vacature schrijven kost een recruiter gemiddeld 3 tot 4 uur: functiebeschrijving opstellen, tone of voice bewaken, formatteren voor verschillende platforms, publiceren. Met n8n en Claude automatiseer je het hele proces. Van functiebeschrijving in Notion tot gepubliceerde vacature op LinkedIn. Wat overblijft is het beoordelen van kandidaten.

Stap 1: Voorbereiding

Je hebt vier dingen nodig: een n8n instance (self-hosted of n8n.cloud), een LinkedIn Developer App met de juiste permissies, een Claude API key en een Notion database voor functiebeschrijvingen.

LinkedIn Developer App aanmaken: Ga naar developers.linkedin.com, maak een nieuwe app aan. Vraag de volgende OAuth scopes aan: r_organization_social, w_member_social, r_liteprofile. Na goedkeuring (1-3 werkdagen) krijg je een client ID en client secret.

# n8n omgevingsvariabelen (.env)
CLAUDE_API_KEY=sk-ant-your_key_here
LINKEDIN_CLIENT_ID=your_client_id
LINKEDIN_CLIENT_SECRET=your_client_secret
LINKEDIN_ORG_URN=urn:li:organization:YOUR_ORG_ID
NOTION_API_KEY=secret_your_notion_key
NOTION_DATABASE_ID=your_database_id
HR_EMAIL=hr@jouwbedrijf.nl

Stap 2: Notion database structuur

Maak een Notion database aan met de volgende eigenschappen (properties). De juiste structuur maakt het verschil tussen een werkend systeem en een bron van fouten.

Verplichte properties: Functietitel (Title), Afdeling (Select), Niveau (Select: Junior/Medior/Senior), Vereisten (Text), Startdatum (Date), Status (Select: Concept/Gereed/Gepubliceerd/Gearchiveerd), Vestiging (Select), Uren per week (Number).

// Notion database schema (API formaat)
{
  "Functietitel": {"title": {}},
  "Afdeling": {"select": {
    "options": [
      {"name": "Marketing", "color": "blue"},
      {"name": "Sales", "color": "green"},
      {"name": "Tech", "color": "purple"},
      {"name": "Operations", "color": "orange"}
    ]
  }},
  "Niveau": {"select": {
    "options": [
      {"name": "Junior"}, {"name": "Medior"}, {"name": "Senior"}
    ]
  }},
  "Status": {"select": {
    "options": [
      {"name": "Concept"}, {"name": "Gereed"},
      {"name": "Gepubliceerd"}, {"name": "Gearchiveerd"}
    ]
  }},
  "Startdatum": {"date": {}},
  "Vereisten": {"rich_text": {}},
  "Uren per week": {"number": {}}
}

Stap 3: n8n Notion-trigger instellen

De n8n workflow start automatisch wanneer iemand de Status van een vacature in Notion wijzigt naar 'Gereed'. Dit is de trigger voor het publicatieproces.

Voeg een Notion Trigger node toe. Kies 'Page updated' als event en filter op de database ID. Voeg een IF node toe die controleert of de Status property gelijk is aan 'Gereed'. Zo triggert de workflow alleen bij de juiste statuswijziging.

// n8n Notion Trigger configuratie (JSON export)
{
  "nodes": [{
    "name": "Notion Trigger",
    "type": "n8n-nodes-base.notionTrigger",
    "parameters": {
      "resource": "page",
      "databaseId": "={{$env.NOTION_DATABASE_ID}}",
      "event": "pageUpdated"
    }
  }, {
    "name": "Check Status",
    "type": "n8n-nodes-base.if",
    "parameters": {
      "conditions": {
        "string": [{
          "value1": "={{$json.properties.Status.select.name}}",
          "operation": "equal",
          "value2": "Gereed"
        }]
      }
    }
  }]
}

Stap 4: Data ophalen en structureren

Na de trigger haal je de volledige vacaturedata op via een Notion node (Get Page). Vervolgens structureer je de data in een Set node zodat volgende nodes eenvoudig met de variabelen kunnen werken.

// Set node - data structureren
{
  "functietitel": "={{$json.properties.Functietitel.title[0].plain_text}}",
  "afdeling": "={{$json.properties.Afdeling.select.name}}",
  "niveau": "={{$json.properties.Niveau.select.name}}",
  "vereisten": "={{$json.properties.Vereisten.rich_text[0].plain_text}}",
  "startdatum": "={{$json.properties.Startdatum.date.start}}",
  "uren": "={{$json.properties['Uren per week'].number}}",
  "vestiging": "={{$json.properties.Vestiging.select.name}}",
  "notion_page_id": "={{$json.id}}"
}

Stap 5: Claude vacaturetekst laten schrijven

Hier gebeurt de magie. Je stuurt de gestructureerde vacaturedata naar Claude met een gedetailleerde system prompt die de bedrijfstoon vastlegt. Claude returnt de vacaturetekst als JSON voor eenvoudige verdere verwerking.

// Claude HTTP Request node configuratie
URL: https://api.anthropic.com/v1/messages
Method: POST
Headers:
  anthropic-version: 2023-06-01
  x-api-key: {{$env.CLAUDE_API_KEY}}

Body:
{
  "model": "claude-opus-4-5",
  "max_tokens": 1500,
  "system": "Je bent een ervaren recruitment copywriter voor [Bedrijfsnaam]. Schrijf vacatureteksten in een professionele maar menselijke toon. Gebruik 'jij' en 'je' (geen 'u'). Vermijd cliches zoals 'no-nonsense', 'hands-on' en 'resultaatgericht'. Focus op wat de kandidaat gaat doen en leren, niet op een endloze lijst eisen. Retourneer ALTIJD een JSON object met keys: linkedin_post (max 700 tekens voor LinkedIn), volledige_vacature (600-800 woorden), en samenvatting (max 200 tekens).",
  "messages": [{
    "role": "user",
    "content": "Schrijf een vacaturetekst voor:\nFunctie: {{functietitel}}\nAfdeling: {{afdeling}}\nNiveau: {{niveau}}\nVereisten: {{vereisten}}\nStartdatum: {{startdatum}}\nUren: {{uren}} uur per week\nVestiging: {{vestiging}}"
  }]
}

Stap 6: Claude output parsen en valideren

Claude retourneert de tekst als JSON-string binnen een message. Gebruik een Code node in n8n om de JSON te parsen en valideren voordat je doorgaat.

// n8n Code node - Claude output parsen
const claudeResponse = items[0].json.content[0].text;

// Parse JSON van Claude
let vacatureData;
try {
  // Claude omringt JSON soms met markdown
  const jsonMatch = claudeResponse.match(/\{[\s\S]*\}/);
  if (jsonMatch) {
    vacatureData = JSON.parse(jsonMatch[0]);
  } else {
    throw new Error('Geen JSON gevonden in Claude response');
  }
} catch (e) {
  throw new Error(`JSON parse fout: ${e.message}\nResponse: ${claudeResponse}`);
}

// Valideer verplichte keys
const required = ['linkedin_post', 'volledige_vacature', 'samenvatting'];
for (const key of required) {
  if (!vacatureData[key]) {
    throw new Error(`Ontbrekende key in Claude output: ${key}`);
  }
}

// Valideer LinkedIn tekst lengte
if (vacatureData.linkedin_post.length > 700) {
  vacatureData.linkedin_post = vacatureData.linkedin_post.substring(0, 697) + '...';
}

return [{ json: vacatureData }];

Stap 7: LinkedIn API aanroepen

De LinkedIn UGC Posts API publiceert de vacature als organisch bericht op de bedrijfspagina. Zorg dat je de organisatie-URN bij de hand hebt (te vinden in LinkedIn Company Admin).

// LinkedIn UGC Posts API - HTTP Request node
URL: https://api.linkedin.com/v2/ugcPosts
Method: POST
Headers:
  Authorization: Bearer {{LinkedIn Access Token}}
  Content-Type: application/json
  X-Restli-Protocol-Version: 2.0.0

Body:
{
  "author": "{{$env.LINKEDIN_ORG_URN}}",
  "lifecycleState": "PUBLISHED",
  "specificContent": {
    "com.linkedin.ugc.ShareContent": {
      "shareCommentary": {
        "text": "{{linkedin_post}}\n\n#vacature #{{afdeling}} #{{niveau}} #werkenbij[Bedrijfsnaam]"
      },
      "shareMediaCategory": "NONE"
    }
  },
  "visibility": {
    "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
  }
}

Stap 8: Bevestiging terugschrijven naar Notion en Gmail-notificatie

Schrijf de resultaten terug naar Notion (status naar 'Gepubliceerd', sla de LinkedIn post-ID op) en stuur een bevestigingsmail naar HR.

// Notion Update Page node
Page ID: {{notion_page_id}}
Properties:
  Status: {
    "select": {"name": "Gepubliceerd"}
  }
  LinkedIn Post ID: {
    "rich_text": [{"text": {"content": "{{linkedin_post_id}}"}}]
  }
  Gepubliceerd op: {
    "date": {"start": "{{$now}}"}
  }

// Gmail Send Email node  
To: {{$env.HR_EMAIL}}
Subject: Vacature gepubliceerd: {{functietitel}}
Body:
De vacature voor {{functietitel}} ({{afdeling}}, {{niveau}}) is succesvol
gepubliceerd op LinkedIn.

LinkedIn post ID: {{linkedin_post_id}}
Bekijk de post: https://www.linkedin.com/feed/update/{{linkedin_post_id}}

De volledige vacaturetekst is beschikbaar in Notion.

Troubleshooting

LinkedIn API rate limits: LinkedIn beperkt UGC Posts tot 100 per dag per app. Voor normale recruitment volumes is dit ruim voldoende. Bij hoge volumes (50+ vacatures per dag): gebruik de LinkedIn Marketing API met een enterprise account.

Token expiry: LinkedIn OAuth tokens verlopen na 60 dagen. Implementeer token refresh logic in n8n: voeg een HTTP Request node toe die het token refresht met de refresh_token voor elke workflow run. Sla het nieuwe access token op in n8n credentials.

Notion webhook delays: De Notion API heeft soms vertraging van 1-5 minuten bij trigger events. Dit is normaal en geen fout. Als je directere triggers nodig hebt, gebruik dan een polling-workflow (elke 5 minuten checken op Status='Gereed') in plaats van een webhook trigger.

Uitbreidingen

Indeed en Glassdoor: Beide platforms bieden API's voor vacatureplaatsing. Indeed heeft een XML feed-systeem; Glassdoor werkt via een partner API. Voeg extra HTTP Request nodes toe na de LinkedIn-publicatie voor gelijktijdig plaatsen.

ATS-koppeling: Koppel het systeem aan een Applicant Tracking System zoals Teamtailor, Recruitee of Homerun. De meeste ATS-tools hebben een REST API. Maak automatisch een vacature aan in het ATS zodra LinkedIn-publicatie succesvol is.

Automatisch reageren op sollicitaties: Bouw een tweede n8n workflow die nieuwe LinkedIn sollicitaties ophaalt (LinkedIn Applications API), een eerste bevestigingsmail stuurt via Claude (gepersonaliseerd op basis van het CV) en de sollicitant toevoegt aan je ATS.

Gerelateerd

Nieuwsbrief

Meer handleidingen en tips?

Elke week de beste agentic AI inzichten en nieuwe handleidingen in je inbox.

Gratis aanmelden →