<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raphaël Pinson</title>
    <description>The latest articles on DEV Community by Raphaël Pinson (@raphink).</description>
    <link>https://dev.to/raphink</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F59811%2Fcdcdbc95-1306-4455-9f79-fa032c300206.jpeg</url>
      <title>DEV Community: Raphaël Pinson</title>
      <link>https://dev.to/raphink</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raphink"/>
    <language>en</language>
    <item>
      <title>Autism and the "genius" effect</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Wed, 22 Apr 2026 06:25:00 +0000</pubDate>
      <link>https://dev.to/raphink/autism-and-the-genius-effect-k86</link>
      <guid>https://dev.to/raphink/autism-and-the-genius-effect-k86</guid>
      <description>&lt;p&gt;This is the tenth post in my autism awareness month series.&lt;/p&gt;

&lt;p&gt;When people think of autism, they often think of Rain Man or Sheldon Cooper. On one end, the severely affected person who needs full-time care. On the other, the socially awkward genius whose extraordinary abilities more than compensate. When I tell friends I am on the spectrum, I often hear: "But you don't look autistic."&lt;/p&gt;

&lt;p&gt;Neither stereotype is wrong exactly. Both exist, but neither is representative. The vast middle is invisible, largely because it masks.&lt;/p&gt;

&lt;p&gt;Before we go further, let's clarify on terminology. "Asperger's syndrome" is no longer a clinical diagnosis: it was folded into the single autism spectrum disorder classification in 2013. It was often used as shorthand for "high-functioning autistic," which is itself problematic, because functioning labels measure visibility of difficulty, not actual experience. The DSM-5 support levels measure required support, not intelligence, not severity of internal experience. A level 1 autistic person isn't mildly autistic. They're autistic in a way that currently requires less visible support, often because they've learned to compensate. That compensation has a cost that isn't measured.&lt;/p&gt;

&lt;p&gt;Now, to address the genius question.&lt;/p&gt;

&lt;p&gt;High IQ and autism are independent variables. Autism doesn't cause exceptional intelligence, and most autistic people don't have exceptional IQ. What is true though is that high-IQ autistic people are disproportionately visible: they function in professional environments, get diagnosed later, and are the face of autism in public discourse. The genius stereotype is largely a visibility problem.&lt;/p&gt;

&lt;p&gt;What autism contributes, independently of IQ, is the constant information-gathering drive that I described two weeks ago in my fourth post. That drive never reaches a point of satisfaction. High IQ adds processing power to that endless process, producing denser cross-domain connections and stronger pattern recognition as a byproduct. The IQ doesn't change the drive. It simply amplifies the output.&lt;/p&gt;

&lt;p&gt;But don't be fooled, this is a double-edged sword! The same wiring that produces unusual thinking also produces the exhaustion, the sensory overload, and the social friction described throughout this series. There is no version that keeps the upside and removes the cost. The consequences are real. But they're located in the mismatch between the wiring and the environment, not in a malfunction. And so the autistic people who seem the better socially adapted (through intellectual masking adjustments) are usually the ones experiencing the most anxiety as a result, and most likely to experience a burnout in their 30s or 40s.&lt;/p&gt;

&lt;p&gt;In short: autism is not a cause of genius, but it can work as an amplifier for high throughput brains.&lt;/p&gt;

&lt;p&gt;Next: on maintaining friendships, and what gets misread as manipulation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7452603793260580865-FQCj" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-22&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>Building MCP Servers for Genealogy: AI-Powered Historical Research</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Tue, 21 Apr 2026 16:58:46 +0000</pubDate>
      <link>https://dev.to/raphink/building-mcp-servers-for-genealogy-ai-powered-historical-research-261p</link>
      <guid>https://dev.to/raphink/building-mcp-servers-for-genealogy-ai-powered-historical-research-261p</guid>
      <description>&lt;p&gt;For years now, I’ve been writing a book tracing four family branches across Europe, the Middle East, and South Africa. One thread follows Louis Rau, my 3rd great-uncle, who was president of Compagnie Continentale Edison (CCE) in the early 1900s. He was an Edison Pioneer, part of the inner circle that brought Edison's electrical systems to Europe.&lt;/p&gt;

&lt;p&gt;Last year, I found that Thomas Edison's papers were digitized at Rutgers University. So I navigated to &lt;a href="http://edisondigital.rutgers.edu" rel="noopener noreferrer"&gt;edisondigital.rutgers.edu&lt;/a&gt;, typed "Louis Rau" into the search box, and hit enter, and 847 results were returned.&lt;/p&gt;

&lt;p&gt;Somewhere in those 847 documents was the correspondence that would explain Louis Rau's business relationship with Élie Moïse Léon, co-founder of CCE. Somewhere were the letters that traced his movements between Paris and Geneva. Somewhere were the details of CCE's electrical installations across Europe.&lt;/p&gt;

&lt;p&gt;But I'd have to click through them one by one, read the snippets, open promising documents, cross-reference dates, take notes, come back later and forget which ones I'd already checked…&lt;/p&gt;

&lt;p&gt;A few weeks ago, I started feeding genealogy documents to Claude AI, but that was still pretty tedious, and I kept hitting image upload limits in conversations. And then it clicked: why not build an MCP server, so Claude could perform the search directly?&lt;/p&gt;

&lt;p&gt;That question became three MCP servers, a transformed research workflow, and a fundamentally different relationship with historical archives.&lt;/p&gt;

&lt;h1&gt;
  
  
  First Win: The Edison Papers MCP
&lt;/h1&gt;

&lt;p&gt;The Edison Papers has an API. I didn't know that initially — I just knew they had a website with a search box. But a quick look at the network tab showed clean REST endpoints returning JSON.&lt;/p&gt;

&lt;p&gt;I opened Claude Code and asked it to build an MCP server that wrapped the Edison Papers API. A few hours of iteration later, I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;edison_search&lt;/code&gt;: Query with field-level precision (&lt;code&gt;creator:"Rau, Louis"&lt;/code&gt;, &lt;code&gt;recipient:"Léon, Élie"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;edison_get_document&lt;/code&gt;: Retrieve full metadata and transcriptions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;edison_browse_series&lt;/code&gt;: Navigate document collections systematically&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;edison_get_images&lt;/code&gt;: Access high-resolution scans&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/raphink" rel="noopener noreferrer"&gt;
        raphink
      &lt;/a&gt; / &lt;a href="https://github.com/raphink/edison-archive-mcp" rel="noopener noreferrer"&gt;
        edison-archive-mcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      MCP server for the Edison Archive
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Edison Papers MCP Server&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An MCP server for querying the &lt;a href="https://edisondigital.rutgers.edu" rel="nofollow noopener noreferrer"&gt;Thomas A. Edison Papers&lt;/a&gt; (Rutgers University) — ~150,000 documents, public domain (CC0).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tools&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;edison_search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full-text search by keyword, author, or recipient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;edison_get_document&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fetch full metadata and transcription for a document by call number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;edison_browse_series&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all documents in an archive series&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Use with Claude.ai (hosted)&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Deploy the server online so Claude.ai can connect to it via HTTP.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Deploy to Railway (free)&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Push this repo to GitHub&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://railway.app" rel="nofollow noopener noreferrer"&gt;railway.app&lt;/a&gt; → &lt;strong&gt;New Project → Deploy from GitHub repo&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your repo, then add this environment variable:
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;MCP_TRANSPORT = http
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
(&lt;code&gt;PORT&lt;/code&gt; is set automatically by Railway)&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy&lt;/strong&gt; (~2 minutes)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Networking → Generate Domain&lt;/strong&gt; to get your public URL&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2. Connect to Claude.ai&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Go to &lt;strong&gt;Claude.ai → Settings → Integrations → Add custom integration&lt;/strong&gt; and enter:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;https://your-app.up.railway.app/mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Use with Claude Desktop (local)&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Install&lt;/h3&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/raphink/edison-archive-mcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Now instead of clicking through 847 results, I could ask Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find correspondence where Louis Rau is the creator, dated 1892-1895, mentioning electrical installations or Paris operations."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And Claude would orchestrate the full research pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt;: Call Edison Papers MCP → retrieve all matching results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triage&lt;/strong&gt;: Read all abstracts, decide which documents warrant full analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track&lt;/strong&gt;: Create a Notion database entry for each document with analysis status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize&lt;/strong&gt;: Rank documents by relevance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep read&lt;/strong&gt;: For priority documents, get high-resolution images and use OCR for full context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary:&lt;/strong&gt; Provide a summary of all findings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What would have taken hours of manual clicking, note-taking, and cross-referencing now happens in one conversation.&lt;/p&gt;

&lt;p&gt;This was immediately useful. But it surfaced a new problem: where do all these findings go?&lt;/p&gt;

&lt;h1&gt;
  
  
  The Organization Problem: Enter Notion MCP
&lt;/h1&gt;

&lt;p&gt;I was already using Notion to organize my research: person profiles, document summaries, research questions. And Claude already had an MCP for Notion.&lt;/p&gt;

&lt;p&gt;So now when I asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Search Edison Papers for Louis Rau correspondence from 1892-1895, create a Notion page summarizing the findings, and link it to Louis Rau's profile."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt;: Call Edison Papers MCP → retrieve all matching results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triage&lt;/strong&gt;: Read all abstracts, decide which documents warrant full analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track&lt;/strong&gt;: Create a Notion database entry for each document with analysis status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize&lt;/strong&gt;: Rank documents by relevance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep read&lt;/strong&gt;: For priority documents, get high-resolution images and use OCR for full context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document&lt;/strong&gt;: Update Notion pages with findings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect&lt;/strong&gt;: Update profile pages for people mentioned (Louis Rau, Élie Léon, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This was amazing. Structured knowledge, automatically organized, all in one conversation.&lt;/p&gt;

&lt;p&gt;But then Claude started hallucinating.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Hallucination Problem: Claude Needs Ground Truth
&lt;/h1&gt;

&lt;p&gt;Claude would find documents mentioning for example Samuel Léon and Élie Léon, and confidently conclude that they that Samuel was Élie's nephew, completely making it up.&lt;/p&gt;

&lt;p&gt;Or it would claim someone was born in 1847 when they were actually born in 1867. Dates off by decades. Family relationships invented wholesale.&lt;/p&gt;

&lt;p&gt;The problem: Claude had access to &lt;em&gt;documents&lt;/em&gt; (via Edison Papers MCP) and &lt;em&gt;research notes&lt;/em&gt; (via Notion MCP), but not the actual genealogy data. It was inferring family structure from fragmentary mentions in letters and my incomplete notes.&lt;/p&gt;

&lt;p&gt;I needed to give Claude access to the tree itself, the actual source of truth about who's related to whom and when they lived.&lt;/p&gt;

&lt;h1&gt;
  
  
  Attempt 1: GEDCOM MCP (Local)
&lt;/h1&gt;

&lt;p&gt;My family tree lives in Geni — a collaborative genealogy platform to build a unique World family tree. Geni has an API, but OAuth kept failing when I tried it and I wanted something working &lt;em&gt;now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So I took a shortcut. From time to time, I export data from Geni to GEDCOM (the genealogy standard format), with about 25000 individuals in my export. I used airy10's GEDCOM MCP to make it queryable locally.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/airy10" rel="noopener noreferrer"&gt;
        airy10
      &lt;/a&gt; / &lt;a href="https://github.com/airy10/GedcomMCP" rel="noopener noreferrer"&gt;
        GedcomMCP
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      MCP Server to create or query GEDCOM files
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;GEDCOM MCP Server&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Genealogy for AI Agents, by AI Agents&lt;/p&gt;
&lt;p&gt;A robust MCP server for creating, editing and querying genealogical data from GEDCOM files
Works great with qwen-cli and gemini-cli&lt;/p&gt;
&lt;p&gt;This project provides a comprehensive set of tools for AI agents to work with family history data
enabling complex genealogical research, data analysis, and automated documentation generation.&lt;/p&gt;
&lt;p&gt;The server has been recently improved with fixes for critical bugs, enhanced error handling,
and better code quality while maintaining full backward compatibility.&lt;/p&gt;
&lt;p&gt;Some sample complex prompts:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;   Load gedcom "myfamily.ged"
   Make a complete, detailled biography of &amp;lt;name of some people from the GEDCOM&amp;gt; and his fammily. Use as much as you can from this genealogy, including any notes from him or his relatives
   You can try to find some info on Internet to complete the document, add some historical or geographic context, etc. Be as complete as possible to tell us a nice&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/airy10/GedcomMCP" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This worked! Now Claude could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search for individuals by name&lt;/li&gt;
&lt;li&gt;Verify relationships ("Is X related to Y?")&lt;/li&gt;
&lt;li&gt;Check birth/death dates&lt;/li&gt;
&lt;li&gt;Trace lineage paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No more hallucinated family connections. The GEDCOM became a &lt;strong&gt;hypothesis database&lt;/strong&gt;, and claims in documents could be verified against known family structure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why Geni as my main database?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I use Geni instead of maintaining a private tree because genealogy is collaborative research. Multiple people contribute information, sources get peer-reviewed, duplicates get merged. A tree on Geni is a &lt;em&gt;shared&lt;/em&gt; knowledge base, not siloed private data that might be duplicated (and wrong) across dozens of individual researchers' files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the GEDCOM approach had limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only works in Claude Desktop (local MCP)&lt;/li&gt;
&lt;li&gt;It requires manually re-exporting GEDCOM whenever the tree updated&lt;/li&gt;
&lt;li&gt;No access in &lt;a href="http://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt; web sessions (or phone)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed the real API.&lt;/p&gt;

&lt;h1&gt;
  
  
  Back to Geni: Tackling OAuth
&lt;/h1&gt;

&lt;p&gt;So I went back to the Geni API. A few more hours of iteration with Claude Code, and I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full OAuth implementation (access tokens, refresh flow)&lt;/li&gt;
&lt;li&gt;13 tools: profile CRUD, relationship pathfinding, merge candidate detection, family traversal&lt;/li&gt;
&lt;li&gt;Search by name, verify relationships, trace lineage paths programmatically&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/raphink" rel="noopener noreferrer"&gt;
        raphink
      &lt;/a&gt; / &lt;a href="https://github.com/raphink/geni-mcp" rel="noopener noreferrer"&gt;
        geni-mcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An MCP Server for Geni
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;geni-mcp&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An MCP (Model Context Protocol) server that gives Claude access to &lt;a href="https://www.geni.com" rel="nofollow noopener noreferrer"&gt;Geni&lt;/a&gt; — the collaborative genealogy platform. Use Claude to browse, search, correct, and extend your family tree.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_authorization_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start the OAuth flow — get the URL to authorize Claude&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exchange_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Complete OAuth — exchange the code for tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_my_profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get your own Geni profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Look up any profile by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;update_profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Correct names, dates, locations, biography&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create_profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a new person to Geni&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_immediate_family&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get parents, siblings, spouses, children&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_relationship_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find relationship path between two profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_union&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get a family unit (couple + children)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_relation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a parent, child, sibling, or spouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_profiles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search by name with optional birth/death filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_merge_candidates&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find potential duplicate profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merge_profiles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Merge a duplicate into a base profile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;A Geni account at &lt;a href="https://www.geni.com" rel="nofollow noopener noreferrer"&gt;geni.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A registered Geni app — create one at &lt;a href="https://www.geni.com/platform/developer/apps" rel="nofollow noopener noreferrer"&gt;geni.com/platform/developer/apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node.js 20+&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Clone &amp;amp;&lt;/h3&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/raphink/geni-mcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Now I could ask mid-conversation: "Is Samuel Léon related to Élie Moïse Léon?" and get the relationship path instantly, whether I was in Claude Desktop or &lt;a href="http://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The tree became &lt;strong&gt;queryable context&lt;/strong&gt; accessible anywhere, not just on my local machine with an up-to-date GEDCOM file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Third Server: Newspapers MCP
&lt;/h1&gt;

&lt;p&gt;With Edison Papers and Geni working, I could trace business connections and verify family relationships. But I was still missing contemporary context: how did the &lt;em&gt;public&lt;/em&gt; see these people? What did newspapers say about CCE's operations? Were there announcements, obituaries, social mentions?&lt;/p&gt;

&lt;p&gt;Historical newspapers are digitized across dozens of national archives. Each has its own interface. Searching them all manually meant opening multiple websites, running the same query in different systems, downloading results individually.&lt;/p&gt;

&lt;p&gt;So I built a newspapers MCP that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aggregates multiple national newspaper archives&lt;/li&gt;
&lt;li&gt;Searches across collections simultaneously&lt;/li&gt;
&lt;li&gt;Returns snippets as base64-encoded images (because OCR quality varies)&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/raphink" rel="noopener noreferrer"&gt;
        raphink
      &lt;/a&gt; / &lt;a href="https://github.com/raphink/newspapers-mcp" rel="noopener noreferrer"&gt;
        newspapers-mcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Newspapers MCP Server&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An MCP (Model Context Protocol) server for searching online newspaper archives across multiple countries and regions. This server provides unified access to newspaper collections from around the world through a single, standardized interface.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supported Archives&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Archive&lt;/th&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Source key&lt;/th&gt;
&lt;th&gt;Full-text search&lt;/th&gt;
&lt;th&gt;OCR text&lt;/th&gt;
&lt;th&gt;Snippet images&lt;/th&gt;
&lt;th&gt;API key&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Europeana Collections&lt;/td&gt;
&lt;td&gt;Europe (multi-country)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;europeana&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Optional (&lt;a href="https://pro.europeana.eu/pages/get-api" rel="nofollow noopener noreferrer"&gt;get key&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gallica (BnF)&lt;/td&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gallica&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deutsche Digitale Bibliothek&lt;/td&gt;
&lt;td&gt;Germany&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ddb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;digiPress (BSB)&lt;/td&gt;
&lt;td&gt;Germany / Bavaria&lt;/td&gt;
&lt;td&gt;&lt;code&gt;digipress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ANNO (Austrian NL)&lt;/td&gt;
&lt;td&gt;Austria / Austro-Hungarian Empire&lt;/td&gt;
&lt;td&gt;&lt;code&gt;anno&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delpher (KB)&lt;/td&gt;
&lt;td&gt;Netherlands&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delpher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chronicling America (LoC)&lt;/td&gt;
&lt;td&gt;United States&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chronicling_america&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eLuxemburgensia (BnL)&lt;/td&gt;
&lt;td&gt;Luxembourg&lt;/td&gt;
&lt;td&gt;&lt;code&gt;eluxemburgensia&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trove (NLA)&lt;/td&gt;
&lt;td&gt;Australia&lt;/td&gt;
&lt;td&gt;&lt;code&gt;trove&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Required (free — &lt;a href="https://trove.nla.gov.au/about/create-something/using-api" rel="nofollow noopener noreferrer"&gt;get key&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Norwegian NL (nb.no)&lt;/td&gt;
&lt;td&gt;Norway&lt;/td&gt;
&lt;td&gt;&lt;code&gt;norwegian&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/raphink/newspapers-mcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Here’s a real example:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I asked Claude to search for "Joseph Dreyfus grain Paris 1895" (a grain merchant in the family who had a financial collapse). The MCP found the &lt;em&gt;concordataire liquidation&lt;/em&gt; announcement in French commercial journals. That single search led to discovering a 90-page Archives de Paris dossier (D14U³/89) I'm still analyzing.&lt;/p&gt;

&lt;p&gt;One search. Ten minutes. What would have been days of archive website navigation.&lt;/p&gt;

&lt;h1&gt;
  
  
  How They Work Together: Finding Solomon Rau in Munich
&lt;/h1&gt;

&lt;p&gt;Here's a recent example showing how the MCPs orchestrate together:&lt;/p&gt;

&lt;p&gt;I asked Claude to search for Solomon Rau's activity in Munich newspapers. The newspapers MCP returned various results, including this advertisement:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ch2earn53443pwxiifw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ch2earn53443pwxiifw.png" alt="DDSG Announcement" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This ad showed Solomon Rau advertising the reimbursement of DDSG (Danube Steam Shipping Company) stock — a discovery that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revealed his business activity (financial/stock trading)&lt;/li&gt;
&lt;li&gt;Connected him to DDSG, a major shipping company&lt;/li&gt;
&lt;li&gt;Provided a concrete date and location (Munich)&lt;/li&gt;
&lt;li&gt;Led to further discoveries about other family members' activities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude then cross-referenced this against the Geni tree to verify Solomon's identity and relationships, and documented the finding in Notion with the newspaper snippet as a source.&lt;/p&gt;

&lt;p&gt;It then correlated it to the DDSG stock that Adolphe Grünberg, Solomon’s son-in-law, had in his post-mortem inventory the next year in 1878, and added another note there.&lt;/p&gt;

&lt;p&gt;Have you built AI integration for research yourself? What were your best findings?&lt;/p&gt;

</description>
      <category>genealogy</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>When authority doesn't compute</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Mon, 20 Apr 2026 06:50:00 +0000</pubDate>
      <link>https://dev.to/raphink/when-authority-doesnt-compute-4bi7</link>
      <guid>https://dev.to/raphink/when-authority-doesnt-compute-4bi7</guid>
      <description>&lt;p&gt;This is the ninth post in my autism awareness month series.&lt;/p&gt;

&lt;p&gt;In the previous post, I described the autistic person who looks at you and smiles while you wait for them to comply with something they know they have to do and simply won't. Part of what produces that smile is the absence of spontaneous facial mimicry. But there's something else behind it: the submission reflex that isn't there.&lt;/p&gt;

&lt;p&gt;For most people, deference to authority is not primarily a conscious decision. It's a conditioned reflex: the body responds before the mind deliberates. Lowered gaze, softened posture, adjusted tone. These happen automatically, as a result of social conditioning that accumulates from early childhood.&lt;/p&gt;

&lt;p&gt;The autistic brain doesn't register this reflex. Not because the social training wasn't there — it often was, extensively. But no amount of training installs a reflex the nervous system doesn't have a slot for. What training produces instead is anxiety: the awareness of doing it wrong, knowing consequences are coming, but no access to the expected behavior. The performance isn't available, only the awareness of its absence is.&lt;/p&gt;

&lt;p&gt;This is why "just learn to respect authority" doesn't work as an instruction. The target behavior isn't a choice being withheld — it's a reflex that isn't firing. Authority without reason doesn't register as authority. It registers as an unsupported assertion, and connects directly to the previous post: the brain that won't perform a task without a valid reason also won't defer to a person without one. Same mechanism, different domain.&lt;/p&gt;

&lt;p&gt;There is an emotional cost associated to this, though it surfaces later rather than in the moment. During a high-load confrontation, the autistic brain is fully occupied processing the external situation. Emotional processing gets deferred. When it finally surfaces — sometimes hours later — it attaches to whatever small thing is happening then. A minor frustration, something completely trivial. The reaction looks disproportionate — such as tears or rage. The connection to the original cause is invisible to everyone watching because of the delay.&lt;/p&gt;

&lt;p&gt;This pattern is particularly visible in children. Most high functioning autistic adults learn over time to defer emotional processing to private moments, but the cost is still there. In undiagnosed adults, these accumulated costs are frequently misread as depression or anxiety, and sometimes treated as such, with interventions that can inadvertently make things worse.&lt;/p&gt;

&lt;p&gt;Understanding the underlying mechanism changes what help actually looks like. I'll cover that more fully in the last post of this series. In the meantime, if any of this resonates, feel free to DM me.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/feed/update/urn:li:share:7451883854363201536/" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-20&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>Tasks that don't make sense</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Fri, 17 Apr 2026 08:15:00 +0000</pubDate>
      <link>https://dev.to/raphink/tasks-that-dont-make-sense-26k3</link>
      <guid>https://dev.to/raphink/tasks-that-dont-make-sense-26k3</guid>
      <description>&lt;p&gt;This is the eighth post in my autism awareness month series.&lt;/p&gt;

&lt;p&gt;There's a pattern many autistic people recognize but rarely name: the inability to perform tasks that don't make sense. Not tasks that are hard, or unpleasant, or boring, but tasks whose purpose doesn't compute.&lt;/p&gt;

&lt;p&gt;This is different from procrastination. Procrastination is knowing you should do something and not doing it. What happens here is closer to a blank: the brain doesn't engage because it hasn't received a valid reason to.&lt;/p&gt;

&lt;p&gt;I studied medicine for two years. My friends would put in ten-hour study days without question, and I couldn't get myself to do the same, not because I was exhausted or distracted, but because the task simply wouldn't engage. When I asked one of them why she worked so hard, she said: because my parents want me to be a doctor. There was no way my brain would let my body work that hard for that reason. It wasn't laziness, I can work intensely when things make sense. The engine just wouldn't start for this.&lt;/p&gt;

&lt;p&gt;The same pattern shows up anywhere social pressure substitutes for genuine reason: everyone else is doing it, you have no choice. Those don't satisfy the brain's actual question: what is the purpose of this, in terms I can evaluate?&lt;/p&gt;

&lt;p&gt;When that answer isn't there, the block isn't reluctance or stubbornness, it's closer to turning the key in a car with no engine. The action is available, the result isn't. What pushing harder produces is something between frustration and despair: the feeling of wanting to move, making the effort, and finding that nothing responds. The will is there. The compliance isn't available. From the outside it looks exactly like not trying.&lt;/p&gt;

&lt;p&gt;And what the observer sees often makes things worse. The autistic person may just look at you and smile, while you wait for them to do something they know they have to do and simply won't. The smile isn't defiance, it's the combined result of two absent automatic systems: the spontaneous facial mimicry that would normally adjust your expression to match the gravity of the situation, and the conscious control over that expression, which isn't available because the brain is already occupied with the block itself.&lt;/p&gt;

&lt;p&gt;There's a strength in this though. The same trait that makes arbitrary tasks impossible makes unnecessary complexity visible. The person who keeps asking "why are we doing this?" in a process review is often the one who finds the actual bottleneck.&lt;/p&gt;

&lt;p&gt;That smile I mentioned above, and what it looks like when authority meets a brain that doesn't have a submission reflex, will be the topic of the next post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7450818870254358529-jfU3" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-17&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>The filter that isn't running</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Wed, 15 Apr 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/raphink/the-filter-that-isnt-running-5d6d</link>
      <guid>https://dev.to/raphink/the-filter-that-isnt-running-5d6d</guid>
      <description>&lt;p&gt;This is the seventh post in my autism awareness month series.&lt;/p&gt;

&lt;p&gt;Last week, in a shop, the music felt loud. It wasn't painful to my ears at all. But it felt like a violation of my mind, occupying space I needed for thinking. I couldn't form a coherent thought. Part of me wanted to scream for it to stop. Instead I chose to step outside.&lt;/p&gt;

&lt;p&gt;That's not a metaphor. That's what happened.&lt;/p&gt;

&lt;p&gt;Here's why. Neurotypical auditory processing includes an automatic attention filter, the brain selects what to process and suppresses the rest. It's why you can follow one conversation in a noisy room, why background music stays in the background. The cocktail party effect. The filter runs without effort or awareness.&lt;/p&gt;

&lt;p&gt;The autistic brain doesn't apply this filter the same way. Everything comes in at full processing weight. Not louder, just unfiltered. My wife could sit in the same shop and simply not notice the music after a few minutes. Not because she was trying to ignore it. Because her brain filed it as background and moved on. Mine didn't.&lt;/p&gt;

&lt;p&gt;This is why "just ignore it" doesn't work as advice. You can't consciously override a central processing response. The sensation isn't at the ear, it's downstream, in what the brain does with the signal. Earplugs help with some sounds but not others, because the problem isn't always volume. Loop earplugs, which I've tried, turned the music into a muffled ASMR mess, a different problem, not a solution.&lt;/p&gt;

&lt;p&gt;The whispering from the previous post fits here too. When I asked my family to speak more quietly, they whispered. That made it worse, because whispering is harder to pay attention to, not easier. The problem wasn't loudness, it was simultaneous signals at full weight, and whispering added effort on top of the load.&lt;/p&gt;

&lt;p&gt;And it's not just sound. The same mechanism applies to any sensory input: touch, light, heat, and less obviously, physical discomfort from hunger, illness, or food sensitivities. My son finds heat the most unbearable. Not because his body overheats differently, but because the thermal signal competes for processing at the same weight as everything else. Once, as a child, he was outside upset because he was hot. My wife told him to get out of the sun. He kept saying he was hot. Moving wouldn't have helped immediately, the signal was already inside, already being processed. The brain doesn't flush on command.&lt;/p&gt;

&lt;p&gt;The same logic applies to diet. Removing dairy helped my son noticeably, not because dairy causes autism, but because a food sensitivity generates internal signals that an unfiltered system has to process. Managing unnecessary inputs is load management, removing them helps with sensory overload. The wiring stays the same. You're just giving it less to handle.&lt;/p&gt;

&lt;p&gt;Next: when the brain won't comply.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7450070803813109760-KbNl/" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-15&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>When the sensory threshold moves</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Mon, 13 Apr 2026 07:53:00 +0000</pubDate>
      <link>https://dev.to/raphink/when-the-sensory-threshold-moves-111p</link>
      <guid>https://dev.to/raphink/when-the-sensory-threshold-moves-111p</guid>
      <description>&lt;p&gt;This is the sixth post in my autism awareness month series. The previous ones tried to explain mechanisms. This one is harder to write, because it's about a typical evening at the dinner table.&lt;/p&gt;

&lt;p&gt;A reader commented on post 3 that the color blindness analogy has a limit: color blindness doesn't fluctuate with how you're feeling or what's happening around you. She's right, and autism does.&lt;/p&gt;

&lt;p&gt;The sensory threshold isn't fixed. It doesn't move day to day. It can move minute to minute. The same sound that was fine an hour ago becomes unbearable. The same touch from the same person that felt fine yesterday is suddenly intolerable. Not because something changed externally, but because the internal load crossed a line that isn't visible from the outside.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice. I'm at dinner with my family. A normal dinner, with normal quiet conversations between two adults and four children. At some point the noise becomes too much: multiple conversations, overlapping voices, a specific pitch. I ask everyone to speak a bit lower, or one at a time. They try to help: they start whispering. This actually makes it worse, because whispering is a different frequency problem, not a volume problem. So I ask again, more precisely this time. Now I'm the one enforcing rules on everyone for what looks like no good reason. I don't want to leave, as I'm genuinely interested in what's happening, and leaving would be rude. So I stay. And eventually I reach a point where I simply can't continue, and I leave the table, not dramatically, but not quietly either, because by that point I've already absorbed more than the system can handle.&lt;/p&gt;

&lt;p&gt;From the outside it looks like an overreaction to nothing. There's no visibly valid reason for me to be upset about the situation. The buildup was invisible.&lt;/p&gt;

&lt;p&gt;The coping strategies most of us have learned — don't leave abruptly, stay engaged, don't make others uncomfortable — are exactly the ones that prevent the only real regulation option available in the moment. The polite thing and the functional thing are in direct conflict. And we've been trained, often the hard way, to choose polite.&lt;/p&gt;

&lt;p&gt;Over time, extended periods of this produce what's called autistic burnout. Not laziness, not mood. A system that has been running above capacity for too long and needs to shut down. For many highly functional autistic people, it often kicks in their 40s, after decades of invisibly costly adjustment.&lt;/p&gt;

&lt;p&gt;Which raises a question: why does the same sound feel fine one moment and unbearable the next? That's what the next post is about.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7449362918422253569/" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-10&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>More autism in IT?</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Fri, 10 Apr 2026 07:34:00 +0000</pubDate>
      <link>https://dev.to/raphink/more-autism-in-it-22l9</link>
      <guid>https://dev.to/raphink/more-autism-in-it-22l9</guid>
      <description>&lt;p&gt;This is the fifth post in a series for autism awareness month.&lt;/p&gt;

&lt;p&gt;Three years ago, I created a channel called &lt;code&gt;#autism&lt;/code&gt; on my company's Slack. I wasn't sure what to expect. On the first day, about 20 people joined, roughly one in six of the company at the time. Four personal testimonies came in within hours. Dozens more wanted to understand better. I later renamed it &lt;code&gt;#neurodiversity&lt;/code&gt; to be more encompassing.&lt;/p&gt;

&lt;p&gt;That wasn't a trend. That was a room full of people who had been waiting for the door to open.&lt;br&gt;
So: is there more autism in IT? Or does IT just make it easier to be autistic?&lt;br&gt;
Probably both, and they're not contradictory.&lt;/p&gt;

&lt;p&gt;My wife worked for years as a psychologist with autistic children. Shortly after our second child was born, she started noticing patterns in him that she recognized professionally. It took ten years to get a formal diagnosis. When the psychiatrist finally explained why our son was on the spectrum, I remember thinking: he's describing me.&lt;/p&gt;

&lt;p&gt;That's not an unusual story. Autism has a well-documented genetic component. Autistic people are more likely to have autistic children, and certain fields, IT among them, have been quietly concentrating people with this neurological profile for decades. Long before most of them had a name for it.&lt;/p&gt;

&lt;p&gt;The cognitive traits that make social navigation harder — needing explicit structure, struggling with unspoken rules, finding small talk costly — are often the same traits that make technical work easier. Pattern recognition, deep focus on narrow problems, a preference for systems that behave predictably, a low tolerance for ambiguity that, in code, is actually a feature.&lt;/p&gt;

&lt;p&gt;IT didn't create more autistic people. It created conditions where autistic people could function, contribute, and sometimes thrive — without anyone necessarily noticing why. The diagnosis rates are rising because awareness is rising, not because something new is happening.&lt;/p&gt;

&lt;p&gt;Next Monday, I'll post about sensory overload and autistic burnout.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_the-constant-background-hum-this-is-the-activity-7447881266655281152-B9nH" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-10&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>The constant background hum</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:52:00 +0000</pubDate>
      <link>https://dev.to/raphink/the-constant-background-hum-hh7</link>
      <guid>https://dev.to/raphink/the-constant-background-hum-hh7</guid>
      <description>&lt;p&gt;This is the fourth post in a series for autism awareness month. Previous posts covered the neurological vs. psychological distinction and what "spectrum" actually means. This week: what runs in the background, all the time.&lt;/p&gt;

&lt;p&gt;My mother often reminds me that when I was a child visiting friends, I would tour every room and every corner of their apartment before I could sit down and play. I had no idea I was doing it. I just couldn't settle until I had a complete map.&lt;br&gt;
That pattern never went away.&lt;/p&gt;

&lt;p&gt;There's a well-documented phenomenon in autism research called intolerance of uncertainty (IU): the nervous system's difficulty tolerating absent information. The research links it directly to how the autistic brain builds predictions: less automatically, less reliably than in neurotypical brains. The result is a system that needs more explicit data to feel oriented. So it collects data, constantly.&lt;/p&gt;

&lt;p&gt;Everyone experiences some anxiety about the unknown. The difference in autism is in the mechanism and the scale. For me, the information-gathering isn't occasional, it's continuous, and it isn't very selective. Any piece of information feels potentially vital. If someone mentions an actor I don't recognize, I need to know who that is. Not out of curiosity, but because there might be a joke about that actor later. Everyone will laugh. I won't follow. And unlike most social awkwardness, I can't fake it convincingly without the underlying data. The gap isn't uncomfortable, it's a predicted failure.&lt;/p&gt;

&lt;p&gt;This is what produces the constant background hum: not anxiety about anything specific, but the nervous system permanently scanning for gaps in the map, because any gap is a potential trapdoor.&lt;/p&gt;

&lt;p&gt;Special interests are the inverse of this. A domain I know deeply is a domain with no trapdoors. The intensity and relief that come with a special interest aren't enthusiasm, they're the feeling of ground that holds. Talking about it at length isn't ignoring the other person, it's the only moment the scanning stops.&lt;/p&gt;

&lt;p&gt;There is an upside to all this collecting though. A brain that has spent decades gathering fragments from unrelated domains ends up with an unusually dense web of cross-references. When something new appears, it rarely arrives in isolation: the system has already indexed something adjacent, something that rhymes, something from three fields over. Pattern recognition and unexpected correlations come almost automatically, not as a skill but as a byproduct of the constant scan. We'll talk more about that in a following post on STEM and IT.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7448263254449131520/" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-09&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>What "autistic spectrum" actually means</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:48:00 +0000</pubDate>
      <link>https://dev.to/raphink/what-autistic-spectrum-actually-means-4djb</link>
      <guid>https://dev.to/raphink/what-autistic-spectrum-actually-means-4djb</guid>
      <description>&lt;p&gt;Last week, I wrote about the difference between a psychological trait and a neurological one, and why autism sits firmly in the second category. This week I want to address something that makes that distinction harder to see: a widespread misconception about what "spectrum" means in the context of autism.&lt;/p&gt;

&lt;p&gt;Most people hear "autism spectrum" and picture a line. At one end, severe autism. At the other, neurotypical. Somewhere in the middle, people like me: a little autistic, but mostly fine. The spectrum as a gradient, a dial you can turn up or down.&lt;/p&gt;

&lt;p&gt;That's not what it means. Here's an analogy that I find clarifying, using vision 👓.&lt;/p&gt;

&lt;p&gt;Most people have some degree of myopia. It's very common, it exists on a continuous scale, and crucially, it's fixable. Glasses, contact lenses, laser surgery. You correct the optics, the problem goes away. Myopia is a vertical spectrum: more or less of the same thing, with a clear correction available.&lt;/p&gt;

&lt;p&gt;Color blindness works differently. It's not that color blind people see less color than everyone else, instead they process color through a different configuration of receptors. There's no dial connecting their vision to standard vision. It's a horizontal spectrum: many different ways of being color blind, each with its own profile, none of them simply "less" than normal. And critically, you can't correct it. You can't give someone new cone receptors. What you can do is design the world differently: accessible interfaces, patterns alongside colors, signage that doesn't rely on red-green distinction alone.&lt;/p&gt;

&lt;p&gt;Autism is more like color blindness than myopia.&lt;/p&gt;

&lt;p&gt;The spectrum in autism isn't a scale from "a bit autistic" to "very autistic" with neurotypical at zero. It's a range of different neurological profiles, all sharing the same underlying difference in how the brain processes certain things — social signals, sensory input, pattern and context — but expressing that difference in very different ways. Some people are overwhelmed by noise; others seek it. Some struggle with eye contact; others make too much. Some have significant language delays; others are relentlessly verbal. Same underlying configuration, very different presentations.&lt;/p&gt;

&lt;p&gt;And like color blindness, it's not correctable — only compensable. You can learn strategies, build workarounds, train yourself to recognize patterns you don't process automatically. Many of us do, invisibly, for years. But you're not fixing the wiring. You're working around it.&lt;/p&gt;

&lt;p&gt;Autism asks for the same shift as color blindness. Not "how do we correct this person" but "how do we design interactions, workplaces, and social environments that don't rely exclusively on automatic social processing that not everyone has."&lt;/p&gt;

&lt;p&gt;That's not a lowering of standards. It's a more accurate picture of human variation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_what-autistic-spectrum-actually-means-activity-7447199002984493056-vzj7" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-07&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>Quirk or wiring?</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:45:00 +0000</pubDate>
      <link>https://dev.to/raphink/quirk-or-wiring-5a4b</link>
      <guid>https://dev.to/raphink/quirk-or-wiring-5a4b</guid>
      <description>&lt;p&gt;April is autism awareness month. Yesterday, I posted a comic strip about navigating social situations as an autistic person. I want to spend this month going deeper, with regular posts on experiencing autism.&lt;/p&gt;

&lt;p&gt;A good place to start is the difference between a psychological trait and a neurological one.&lt;/p&gt;

&lt;p&gt;A lot of autistic experiences look like personality traits from the outside. Introversion. Shyness. Social awkwardness. These are things many people relate to, and that relatability is both a bridge and a trap. It creates the impression we're talking about the same thing, just more so. They are also not helped by the common misconception that 'spectrum' means a "vertical" scale from normal to autistic, rather than a "horizontal" one within the neurological condition of autism.&lt;/p&gt;

&lt;p&gt;My wife has transverse myelitis, a condition where the myelin sheath around the spinal cord degrades, disrupting nerve signals. The result: both motor and sensory pathways are affected, and the automatic reflex arc that normally catches you when you stumble overreacts and causes her to fall. Everybody trips. Most people catch themselves without thinking. She doesn't have that fallback. She can scan the ground, plan every step, and still fall, because the pebble she didn't see hits a system that can no longer compensate automatically. The effort is real, the safety net is not present, and the fall unavoidable.&lt;/p&gt;

&lt;p&gt;Autism works on a different system — social cognition rather than motor control — but the structure is the same. Everybody has awkward social moments. Most people recover instinctively, the automatic social circuitry recalibrates. That fallback is what I don't have reliably. I can prepare, study the signals, pay close attention, and still miss something obvious to everyone else, because it happened in a channel I'm not wired to process automatically.&lt;/p&gt;

&lt;p&gt;The exhaustion isn't from interacting. It's from running manual what most people run on autopilot.&lt;/p&gt;

&lt;p&gt;And here's the point that matters: my wife's myelin isn't going to grow back. Physiotherapy helps. A cane helps with balance. But they're compensations for something that isn't there, not a cure for something that went wrong. Autism is the same. You can learn strategies, build workarounds, develop compensations, and many of us do, invisibly, for decades. But you're not fixing the wiring. You're working around it.&lt;/p&gt;

&lt;p&gt;When relating to autistic people, don't assume similar external signs mean the same internal experience and causes. Ask, and be surprised by what you'll find.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_quirk-or-wiring-april-is-autism-awareness-activity-7445840592963653632-kzMB" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-03&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>Improvisation on the Spectrum</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:36:00 +0000</pubDate>
      <link>https://dev.to/raphink/improvisation-on-the-spectrum-1k8i</link>
      <guid>https://dev.to/raphink/improvisation-on-the-spectrum-1k8i</guid>
      <description>&lt;p&gt;As you may know, this month is autism awareness month.&lt;/p&gt;

&lt;p&gt;Many things will be posted about autism, and I'd like to share one: a little comic strip I made in July last year, in an attempt to capture of one many invisible quirks in the daily life of an autistic person.&lt;/p&gt;


&lt;div class="ltag-slides ltag-slides--carousel"&gt;
  &lt;div class="ltag-slides__track"&gt;
    &lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfjs9nojxznk8qumnbuq.png" alt="Intro panel" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1yvgfrz96f761i4do8g.png" alt="Panel 1" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvz3hyk6xuqvyo6tw9m2.png" alt="Panel 2" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4gmr7dmthjwyyw93khm.png" alt="Panel 3" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbn52o4wqahc4woyfh9l.png" alt="Panel 4" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3ju50fm4epuygnoec0n.png" alt="Panel 5" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yeiyvw35h3c5ccwnioj.png" alt="Panel 6" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqqn2vaiq2zywmnkyg0s.png" alt="Panel 7" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12chseq188pv27jnz3ov.png" alt="Panel 8" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbu90ner1oc2b12okefgl.png" alt="Panel 9" width="800" height="800"&gt;
&lt;/div&gt;



  &lt;/div&gt;
    ‹
    ›
    &lt;div class="ltag-slides__dots"&gt;&lt;/div&gt;
    
      (function() {
        var container = document.currentScript.closest('.ltag-slides--carousel');
        var track = container.querySelector('.ltag-slides__track');
        var slides = track.querySelectorAll('.ltag-slide');
        var prevBtn = container.querySelector('.ltag-slides__nav--prev');
        var nextBtn = container.querySelector('.ltag-slides__nav--next');
        var dotsContainer = container.querySelector('.ltag-slides__dots');
        var current = 0;
        var total = slides.length;

        for (var i = 0; i &amp;lt; total; i++) {
          var dot = document.createElement('button');
          dot.className = 'ltag-slides__dot' + (i === 0 ? ' ltag-slides__dot--active' : '');
          dot.setAttribute('aria-label', 'Go to slide ' + (i + 1));
          dot.dataset.index = i;
          dot.addEventListener('click', function() { goTo(parseInt(this.dataset.index)); });
          dotsContainer.appendChild(dot);
        }

        function goTo(index) {
          current = ((index % total) + total) % total;
          track.style.transform = 'translateX(-' + (current * 100) + '%)';
          var dots = dotsContainer.querySelectorAll('.ltag-slides__dot');
          for (var i = 0; i &amp;lt; dots.length; i++) {
            dots[i].classList.toggle('ltag-slides__dot--active', i === current);
          }
        }

        prevBtn.addEventListener('click', function() { goTo(current - 1); });
        nextBtn.addEventListener('click', function() { goTo(current + 1); });
      })();
    
&lt;/div&gt;


&lt;p&gt;This is the first of a series of posts on autism that I will be posting this month.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of my April 2026 autism awareness month series. First published &lt;a href="https://www.linkedin.com/posts/raphink_improvisation-on-the-spectrum-activity-7445480530982100992-3HxX" rel="noopener noreferrer"&gt;on LinkedIn on 2026-04-02&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>inclusion</category>
      <category>community</category>
      <category>career</category>
    </item>
    <item>
      <title>How to Automatically Issue Badges for Instruqt Labs</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 17 Oct 2024 09:00:00 +0000</pubDate>
      <link>https://dev.to/raphink/how-to-automatically-issue-badges-for-instruqt-labs-18k5</link>
      <guid>https://dev.to/raphink/how-to-automatically-issue-badges-for-instruqt-labs-18k5</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"&gt;first blog post&lt;/a&gt;, we talked about making labs fun and enjoyable by adding elements of gamification. Issuing badges is a fantastic way to motivate learners, giving them a sense of accomplishment for the skills they've gained, and Isovalent issues &lt;a href="https://www.credly.com/organizations/isovalent/badges" rel="noopener noreferrer"&gt;hundreds of them&lt;/a&gt; every month for the Cilium labs!&lt;/p&gt;

&lt;h1&gt;
  
  
  Issuing Credentials
&lt;/h1&gt;

&lt;p&gt;Obviously, issuing badges can be done manually, but this is not scalable or ideal for creating a seamless experience. So, let's automate it!&lt;/p&gt;

&lt;p&gt;Credly is a widely recognized provider of digital badges, so we will be using this solution to issue badges whenever a user finishes an Instruqt lab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhqgqjpejwb8q0wf12fr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhqgqjpejwb8q0wf12fr.png" alt="Who doesn't love earning badges‽" width="554" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll be using Instruqt webhooks, coupled with the Credly API, to automatically issue badges when labs are completed. &lt;/p&gt;

&lt;p&gt;And thanks to Isovalent's open-sourced Go libraries for both &lt;a href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;Instruqt&lt;/a&gt; and &lt;a href="https://github.com/isovalent/credly-go" rel="noopener noreferrer"&gt;Credly&lt;/a&gt; APIs, you will find this automation process smooth and straightforward.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/isovalent" rel="noopener noreferrer"&gt;
        isovalent
      &lt;/a&gt; / &lt;a href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;
        instruqt-go
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Go library for the Instruqt API
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;instruqt-go&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://goreportcard.com/report/github.com/isovalent/instruqt-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fa8b48a9905bc6a9c6ec4c7cd213a8af24ec3cf219160155f7248e622ebe931c/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f69736f76616c656e742f696e7374727571742d676f" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://pkg.go.dev/github.com/isovalent/instruqt-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/133e5d01e865ae5bb4e38bb953b897bc2cce3453a28187bcfec71b6d88769b7b/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f69736f76616c656e742f696e7374727571742d676f2e737667" alt="Go Reference"&gt;&lt;/a&gt;
&lt;a href="https://github.com/isovalent/instruqt-goLICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/859a1a0bc85ce8bbd7a730a274fec5c9e77c4726ffdf6aa762a78685e26033a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667" alt="License: Apache 2.0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;instruqt-go&lt;/code&gt; is a Go client library for interacting with the Instruqt platform. It provides a simple and convenient way to programmatically access Instruqt's APIs, manage content, retrieve user data and track information.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manage Instruqt Teams and Challenges&lt;/strong&gt;: Retrieve team information, challenges, and user progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To install the &lt;code&gt;instruqt-go&lt;/code&gt; library, run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go get github.com/isovalent/instruqt-go&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-go notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt; main
&lt;span class="pl-k"&gt;import&lt;/span&gt; (
    &lt;span class="pl-s"&gt;"github.com/isovalent/instruqt-go/instruqt"&lt;/span&gt;
    &lt;span class="pl-s"&gt;"cloud.google.com/go/logging"&lt;/span&gt;
)

&lt;span class="pl-k"&gt;func&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;() {
    &lt;span class="pl-c"&gt;// Initialize the Instruqt client&lt;/span&gt;
    &lt;span class="pl-s1"&gt;client&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;instruqt&lt;/span&gt;.&lt;span class="pl-en"&gt;NewClient&lt;/span&gt;(&lt;span class="pl-s"&gt;"your-api-token"&lt;/span&gt;, &lt;span class="pl-s"&gt;"your-team-slug"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Get all tracks&lt;/span&gt;
    &lt;span class="pl-s1"&gt;tracks&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;GetTracks&lt;/span&gt;()

    &lt;span class="pl-c"&gt;// Add context to calls&lt;/span&gt;
    &lt;span class="pl-s1"&gt;ctx&lt;/span&gt;, &lt;span class="pl-s1"&gt;cancel&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;WithTimeout&lt;/span&gt;(&lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;Background&lt;/span&gt;(), &lt;span class="pl-c1"&gt;5&lt;/span&gt;&lt;span class="pl-c1"&gt;*&lt;/span&gt;&lt;span class="pl-s1"&gt;time&lt;/span&gt;.&lt;span class="pl-c1"&gt;Second&lt;/span&gt;)
    &lt;span class="pl-k"&gt;defer&lt;/span&gt; &lt;span class="pl-en"&gt;cancel&lt;/span&gt;()
    
    &lt;span class="pl-s1"&gt;clientWithTimeout&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;WithContext&lt;/span&gt;(&lt;span class="pl-s1"&gt;ctx&lt;/span&gt;)
    &lt;span class="pl-s1"&gt;userInfo&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;clientWithTimeout&lt;/span&gt;.&lt;span class="pl-en"&gt;GetUserInfo&lt;/span&gt;(&lt;span class="pl-s"&gt;"user-id"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Attach a logger&lt;/span&gt;
    &lt;span class="pl-s1"&gt;logClient&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;logging&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/isovalent" rel="noopener noreferrer"&gt;
        isovalent
      &lt;/a&gt; / &lt;a href="https://github.com/isovalent/credly-go" rel="noopener noreferrer"&gt;
        credly-go
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Go library for the Credly API
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;credly-go&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://goreportcard.com/report/github.com/isovalent/credly-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/03c4cbc4215dd3733bb43dc9dde35733ad89e284ea906811f2fb7c8c6e5140f9/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f69736f76616c656e742f637265646c792d676f" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://pkg.go.dev/github.com/isovalent/credly-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/751d47251770c3f3894e84072b06167a6f584bdfb17ac728f1054b140ef5d4d3/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f69736f76616c656e742f637265646c792d676f2e737667" alt="Go Reference"&gt;&lt;/a&gt;
&lt;a href="https://github.com/isovalent/credly-goLICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/859a1a0bc85ce8bbd7a730a274fec5c9e77c4726ffdf6aa762a78685e26033a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667" alt="License: Apache 2.0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;credly-go&lt;/code&gt; is a Go client library for interacting with the Credly platform. It provides a simple and convenient way to programmatically access Credly's APIs and handle badges and templates.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Badge Management&lt;/strong&gt;: Issue, retrieve, and manage badges using the Credly API.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To install the &lt;code&gt;credly-go&lt;/code&gt; library, run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go get github.com/isovalent/credly-go&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-go notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt; main

&lt;span class="pl-k"&gt;import&lt;/span&gt; (
    &lt;span class="pl-s"&gt;"github.com/isovalent/credly-go/credly"&lt;/span&gt;
)

&lt;span class="pl-k"&gt;func&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;() {
    &lt;span class="pl-c"&gt;// Initialize the Credly client&lt;/span&gt;
    &lt;span class="pl-s1"&gt;client&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;credly&lt;/span&gt;.&lt;span class="pl-en"&gt;NewClient&lt;/span&gt;(&lt;span class="pl-s"&gt;"your-api-token"&lt;/span&gt;, &lt;span class="pl-s"&gt;"your-credly-org"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Get all badges for user joe@example.com&lt;/span&gt;
    &lt;span class="pl-s1"&gt;badges&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;GetBadges&lt;/span&gt;(&lt;span class="pl-s"&gt;"joe@example.com"&lt;/span&gt;)
}&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;We welcome contributions! Please follow these steps to contribute:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fork the repository.&lt;/li&gt;
&lt;li&gt;Create a new branch with your feature or bug fix.&lt;/li&gt;
&lt;li&gt;Make your changes and add tests.&lt;/li&gt;
&lt;li&gt;Submit a pull request with a detailed description of your changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Running Tests&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;To run the tests, use:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go &lt;span class="pl-c1"&gt;test&lt;/span&gt; ./...&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Make sure…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/isovalent/credly-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;In this post, we'll take you step by step through the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up the environment and harnessing Google Cloud Functions.&lt;/li&gt;
&lt;li&gt;Initializing imports, constants, and setting up secret environment variables.&lt;/li&gt;
&lt;li&gt;Implementing the webhook and explaining each step.&lt;/li&gt;
&lt;li&gt;Setting up the webhook in Instruqt and adding signature verification to secure it.&lt;/li&gt;
&lt;li&gt;Testing locally using Docker and Docker Compose.&lt;/li&gt;
&lt;li&gt;Deploying the webhook and required secrets to Google Cloud Platform.&lt;/li&gt;
&lt;li&gt;Wrapping up with some final considerations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;As for the first blog post, you will need an Instruqt account (with an API key) and a Google Cloud project.&lt;/p&gt;

&lt;p&gt;In addition, you will also need a Credly account with an API key this time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Environment
&lt;/h2&gt;

&lt;p&gt;First, create a directory for your function and initialize the Go environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;instruqt-webhook
&lt;span class="nb"&gt;cd &lt;/span&gt;instruqt-webhook

go mod init example.com/labs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just as in the &lt;a href="https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"&gt;first post&lt;/a&gt;, we create a &lt;code&gt;cmd&lt;/code&gt; directory so we can build and test the function locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;cmd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;main.go&lt;/code&gt; file in that directory, with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="c"&gt;// Blank-import the function package so the init() runs&lt;/span&gt;
    &lt;span class="c"&gt;// Adapt if you replaced example.com earlier&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"example.com/labs"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use PORT environment variable, or default to 8080.&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;funcframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"funcframework.Start: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to the &lt;code&gt;instruqt-webhook&lt;/code&gt; directory, create a file named &lt;code&gt;webhook.go&lt;/code&gt; to contain the function logic. This file will serve as the webhook handler for incoming events from Instruqt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Basics
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;webhook.go&lt;/code&gt;, begin by adding the necessary imports, constants, and initializing the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;labs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/GoogleCloudPlatform/functions-framework-go/functions"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/isovalent/instruqt-go/instruqt"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/isovalent/credly-go/credly"&lt;/span&gt;

&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"InstruqtWebhookCatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtWebhookCatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;instruqtTeam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"yourInstruqtTeam"&lt;/span&gt;   &lt;span class="c"&gt;// Replace with your own team name&lt;/span&gt;
    &lt;span class="n"&gt;credlyOrg&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"yourCredlyOrg"&lt;/span&gt;      &lt;span class="c"&gt;// Replace with your own credly organization ID&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing the Webhook Receiver
&lt;/h2&gt;

&lt;p&gt;Now, let's write the &lt;code&gt;instruqtWebhookCatch&lt;/code&gt; function to receive the event.&lt;/p&gt;

&lt;p&gt;We will take advantage of the methods provided by the Isovalent &lt;code&gt;instruqt-go&lt;/code&gt; library to manage the Instruqt webhook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;instruqtWebhookCatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;webhookSecret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSTRUQT_WEBHOOK_SECRET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wbHandler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processWebhook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wbHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function works as a proxy between the HTTP connection handler provided by the Google Cloud Functions framework and the &lt;code&gt;instruqt.HandleWebhook&lt;/code&gt; method provided by Isovalent's library to manage the Svix webhook.&lt;/p&gt;

&lt;p&gt;It allows us to set up a webhook manager by passing the webhook's secret. We will see later where to find the value for the webhook secret.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;instruqt.HandleWebhook&lt;/code&gt; method will automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the webhook signature using svix.&lt;/li&gt;
&lt;li&gt;Parse the incoming event payload.&lt;/li&gt;
&lt;li&gt;Check if the event is valid.&lt;/li&gt;
&lt;li&gt;Retrieve the information into an instruqt.WebhookEvent structure.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4: The &lt;code&gt;processWebhook()&lt;/code&gt; Function
&lt;/h2&gt;

&lt;p&gt;Next, we need to implement the &lt;code&gt;processWebhook&lt;/code&gt; function, where our logic will be placed.&lt;/p&gt;

&lt;p&gt;This function will receive 3 parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the HTTP connection handlers (&lt;code&gt;http.ResponseWriter&lt;/code&gt; and &lt;code&gt;*http.Request&lt;/code&gt;) inherited from the GCP Function handler;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;instruqt.Webhook&lt;/code&gt; structure parsed by &lt;code&gt;instruqt.HandleWebhook&lt;/code&gt; and passed down to us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the complete implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;processWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebhookEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Return early if the event type is not track.completed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"track.completed"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Setup the Instruqt client&lt;/span&gt;
    &lt;span class="n"&gt;instruqtToken&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSTRUQT_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;instruqtToken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;instruqtClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruqtToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtTeam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Setup the Credly client&lt;/span&gt;
    &lt;span class="n"&gt;credlyToken&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREDLY_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;credlyToken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;credlyClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;credly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credlyToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credlyOrg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Get user info from Instruqt&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get user info: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Get track details to extract badge template ID from tags&lt;/span&gt;
    &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTrackById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get track info: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Extract badge template ID from track tags&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;templateId&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrackTags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Use strings.Split to parse the tag and extract the badge template ID&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"badge"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;templateId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;templateId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No badge template ID found for track %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Issue badge through Credly&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;badgeErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;credlyClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssueBadge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// Check if the badge has already been issued&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;badgeErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;badgeErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;credly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrBadgeAlreadyIssued&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Badge already issued for %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusConflict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to issue badge: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;badgeErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the event is of type track.completed, exit otherwise.&lt;/li&gt;
&lt;li&gt;Instantiate Instruqt and Credly clients using environment variables for the tokens.&lt;/li&gt;
&lt;li&gt;Retrieve user information from the Instruqt API. This requires to ensure that Instruqt has that information. See the first blog post to find how to do that with a proxy.&lt;/li&gt;
&lt;li&gt;Get track information from Instruqt. We will use set a badge: special tag on the track to store the Credly badge ID to issue.&lt;/li&gt;
&lt;li&gt;Parse track tags to find the badge template ID.&lt;/li&gt;
&lt;li&gt;Issue the badge using the Credly library.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up the Webhook on Instruqt
&lt;/h2&gt;

&lt;p&gt;To enable Instruqt to call your webhook, navigate to the Instruqt UI, go to Settings -&amp;gt; Webhooks, and click "Add Endpoint" to set up a new webhook that points to your Google Cloud Function URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F962yseu7n7pgiwbauwyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F962yseu7n7pgiwbauwyk.png" alt="Create Webhook" width="800" height="882"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;code&gt;track.completed&lt;/code&gt; in the list of events to fire up this endpoint.&lt;/p&gt;

&lt;p&gt;Since we'll be hosting the function on Google Cloud Functions, the URL will be in the form &lt;code&gt;https://&amp;lt;zone&amp;gt;-&amp;lt;project&amp;gt;.cloudfunctions.net/&amp;lt;name&amp;gt;&lt;/code&gt;. For example, if your function is called &lt;code&gt;instruqt-webhook&lt;/code&gt; and is deployed in the &lt;code&gt;labs&lt;/code&gt; GCP project in the &lt;code&gt;europe-west1&lt;/code&gt; zone, then the URL will be &lt;code&gt;https://europe-west1-labs.cloudfunctions.net/instruqt-webhook&lt;/code&gt;. If in doubt, put a fake URL and you can modify it later.&lt;/p&gt;

&lt;p&gt;Create "Create", then locate the "Signing secret" field to the right side of the panel and copy its value.&lt;/p&gt;

&lt;p&gt;Export it in your terminal as the &lt;code&gt;INSTRUQT_WEBHOOK_SECRET&lt;/code&gt; value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INSTRUQT_WEBHHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whsec_v/somevalueCopiedFromUi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use it to create a new GCP secret called &lt;code&gt;instruqt-webhook-secret&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTRUQT_WEBHHOOK_SECRET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | gcloud secrets create instruqt-webhook-secret &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it the proper permissions to be usable in your function (see first blog post for details):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud projects describe &lt;span class="si"&gt;$(&lt;/span&gt;gcloud config get-value project&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"value(projectNumber)"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
gcloud secrets add-iam-policy-binding instruqt-webhook-secret &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-compute@developer.gserviceaccount.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also create a secret for your Credly token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CREDLY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yourCredlyToken
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CREDLY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | gcloud secrets create credly-token &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
gcloud secrets add-iam-policy-binding credly-token &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-compute@developer.gserviceaccount.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the Code
&lt;/h2&gt;

&lt;p&gt;Let's check that this function builds and runs fine.&lt;/p&gt;

&lt;p&gt;First, update your &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; files with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get ./...
go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FUNCTION_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;InstruqtWebhookCatch go run ./cmd/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function should compile and run fine. You can try sending queries to it on &lt;code&gt;localhost:8080&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect to get an error since the Svix webhook authentication is not set up properly in the payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 08 Oct 2024 13:20:47 GMT
Content-Length: 23

Invalid request method
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be possible to emulate this, but it's a bit complex, so let's just deploy to GCP now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative testing: using Docker
&lt;/h2&gt;

&lt;p&gt;If you'd like to use Docker to test your function locally, you can create a &lt;code&gt;Dockerfile&lt;/code&gt; in your current directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.23&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; myapp ./cmd/main.go

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DEV=true&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8080&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; $PORT&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;INSTRUQT_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${INSTRUQT_WEBHOOK_SECRET}&lt;/span&gt;
      &lt;span class="na"&gt;INSTRUQT_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${INSTRUQT_TOKEN}&lt;/span&gt;
      &lt;span class="na"&gt;CREDLY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CREDLY_TOKEN}&lt;/span&gt;
      &lt;span class="na"&gt;FUNCTION_TARGET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InstruqtWebhookCatch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, build and launch your container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can send requests to &lt;code&gt;localhost:8080&lt;/code&gt; just the same as before!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the Function
&lt;/h2&gt;

&lt;p&gt;You can then deploy the function (adapt the region if needed), giving it access to all three secret values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud functions deploy &lt;span class="s2"&gt;"instruqt-webhook"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gen2&lt;/span&gt; &lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;go122 &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1 &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--entry-point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"InstruqtWebhookCatch"&lt;/span&gt; &lt;span class="nt"&gt;--trigger-http&lt;/span&gt; &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"INSTRUQT_WEBHOOK_SECRET=instruqt-webhook-secret:latest"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"INSTRUQT_TOKEN=instruqt-token:latest"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"CREDLY_TOKEN=credly-token:latest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will upload and build your project, and return the URL to access the function.&lt;/p&gt;

&lt;p&gt;If necessary, update the URL in your Instruqt webhook configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Now for the moment of truth: testing!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a badge on Credly. Publish it and copy its template ID.&lt;/li&gt;
&lt;li&gt;Add a tag to the Instruqt track you want to associate the badge with. Name the tag &lt;code&gt;badge:&amp;lt;template_ID&amp;gt;&lt;/code&gt;, replacing &lt;code&gt;template_ID&lt;/code&gt; with the ID you just copied.&lt;/li&gt;
&lt;li&gt;Publish the track.&lt;/li&gt;
&lt;li&gt;Take the track and complete it!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should get the badge in your email!&lt;/p&gt;

&lt;h1&gt;
  
  
  Further Considerations
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Information&lt;/strong&gt;: Make sure you read the &lt;a href="https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"&gt;first blog post&lt;/a&gt; to understand how to send user information to Instruqt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make it worth it!&lt;/strong&gt;: Getting badges is fun, but it's better if users deserve them. Consider adding exam steps to your tracks to make earning the badges a challenge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting and Retries&lt;/strong&gt;: Consider rate limiting incoming webhook requests to prevent abuse and adding retry logic to handle temporary failures when interacting with Credly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage more Events&lt;/strong&gt;: This webhook manager only manages &lt;code&gt;track.completed&lt;/code&gt; events. You can extend it to do a lot more things with all the events provided by Instruqt! I typically like to capture lots of events to send them to Slack for better visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt;: Consider adding more logging (for example using the GCP logging library) to the code.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>instruqt</category>
      <category>credly</category>
      <category>devrel</category>
      <category>go</category>
    </item>
  </channel>
</rss>
