<?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>Forem: Peakline Studio</title>
    <description>The latest articles on Forem by Peakline Studio (@peaklinestudio).</description>
    <link>https://forem.com/peaklinestudio</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%2F3847885%2F9dbfa59a-2949-425c-a3c8-bfe575b06124.png</url>
      <title>Forem: Peakline Studio</title>
      <link>https://forem.com/peaklinestudio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/peaklinestudio"/>
    <language>en</language>
    <item>
      <title>webhook.site Alternatives in 2026: Free Tools for Testing and Inspecting Webhooks</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Mon, 30 Mar 2026 17:18:03 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/webhooksite-alternatives-in-2026-free-tools-for-testing-and-inspecting-webhooks-5f5n</link>
      <guid>https://forem.com/peaklinestudio/webhooksite-alternatives-in-2026-free-tools-for-testing-and-inspecting-webhooks-5f5n</guid>
      <description>&lt;p&gt;Webhooks are everywhere in 2026 — Stripe, GitHub, Slack, Shopify, Twilio, and hundreds of other services push events to your endpoints. But testing them without a running server is annoying. That's where hosted webhook inspection tools come in.&lt;/p&gt;

&lt;p&gt;This post compares the main options in 2026, so you can pick the right tool for your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually need from a webhook testing tool
&lt;/h2&gt;

&lt;p&gt;Before comparing, let's nail down the requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unique inspection URL&lt;/strong&gt; — a real HTTPS endpoint that accepts any HTTP method&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time request viewing&lt;/strong&gt; — see headers, body, query params instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload replay&lt;/strong&gt; — resend captured payloads to your local server without re-triggering the original event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt; — saved history for debugging after the fact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transform rules&lt;/strong&gt; — filter or mask sensitive data before storing (useful for Stripe payloads with card data)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The main free options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  webhook.site
&lt;/h3&gt;

&lt;p&gt;The OG. Generates a unique URL instantly, no account needed. Clean UI, shows headers, body, and query params in real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free plan limits&lt;/strong&gt;: 50 requests per URL, URLs expire. Paid plans start at $7.50/month for unlimited requests and workflows.&lt;/p&gt;

&lt;p&gt;Best for: quick one-off tests where you just need to confirm a webhook fired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not great for&lt;/strong&gt;: long-running debug sessions, replaying payloads, or teams sharing endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  RequestBin (via Pipedream)
&lt;/h3&gt;

&lt;p&gt;RequestBin was acquired by Pipedream and relaunched as part of their platform. It still gives you a free inspection URL, but it's now deeply integrated with Pipedream's workflow automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free plan limits&lt;/strong&gt;: limited event history, account required.&lt;/p&gt;

&lt;p&gt;Best for: developers already using Pipedream who want to chain webhooks into automations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not great for&lt;/strong&gt;: pure webhook inspection — the UI has become automation-first.&lt;/p&gt;

&lt;h3&gt;
  
  
  ngrok (inspect mode)
&lt;/h3&gt;

&lt;p&gt;ngrok is primarily a tunnel to localhost, but its web inspector at &lt;code&gt;localhost:4040&lt;/code&gt; is excellent for debugging webhooks hitting your local dev environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free plan limits&lt;/strong&gt;: ngrok URLs change on restart (unless paid), connection limits apply.&lt;/p&gt;

&lt;p&gt;Best for: developers who need to test against a locally running service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not great for&lt;/strong&gt;: sharing URLs with external services or when you don't want to keep a local process running.&lt;/p&gt;

&lt;h3&gt;
  
  
  HookTest
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hooktest.peakline-ops.workers.dev" rel="noopener noreferrer"&gt;HookTest&lt;/a&gt; is a newer option that addresses some of the gaps in the tools above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free plan&lt;/strong&gt;: 3 endpoints, 24-hour TTL, real-time inspection — no account needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's different&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payload replay&lt;/strong&gt; — resend any captured webhook to a different target URL. Useful when you want to replay a Stripe event to your local &lt;code&gt;ngrok&lt;/code&gt; endpoint without re-triggering the actual Stripe flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transform rules&lt;/strong&gt; — mask, rename, or filter fields in payloads before they're stored. Handy when Stripe payloads include partial card data and you want to avoid logging it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Works without ngrok&lt;/strong&gt; — endpoints are real HTTPS URLs with no local process required.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pro plan ($9/month)&lt;/strong&gt;: 10 persistent endpoints, transform rules, extended history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;webhook.site&lt;/th&gt;
&lt;th&gt;RequestBin&lt;/th&gt;
&lt;th&gt;ngrok inspect&lt;/th&gt;
&lt;th&gt;HookTest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No account needed&lt;/td&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;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time inspect&lt;/td&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;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload replay&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓ (local only)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transform/mask fields&lt;/td&gt;
&lt;td&gt;✗ (paid)&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent history&lt;/td&gt;
&lt;td&gt;✗ (free)&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗ (free) / ✓ (pro)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom domain&lt;/td&gt;
&lt;td&gt;✗ (free)&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓ (paid)&lt;/td&gt;
&lt;td&gt;upcoming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free request limit&lt;/td&gt;
&lt;td&gt;50/URL&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;unlimited (local)&lt;/td&gt;
&lt;td&gt;100/endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing the right tool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quick one-time test&lt;/strong&gt; → webhook.site (fastest, no friction)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local development + debugging&lt;/strong&gt; → ngrok&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow automation&lt;/strong&gt; → RequestBin via Pipedream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay, masking, team use&lt;/strong&gt; → HookTest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real workflow killer is having to re-trigger events from the source to debug a payload parsing issue. With replay, you capture the event once and resend it as many times as you need — even to a different URL (like your localhost).&lt;/p&gt;




&lt;p&gt;Have a different webhook testing tool you swear by? Drop it in the comments.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>devtools</category>
      <category>testing</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Auto-Generate API Documentation from Any GitHub Repo (Without Writing a Word)</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Mon, 30 Mar 2026 17:17:55 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/how-to-auto-generate-api-documentation-from-any-github-repo-without-writing-a-word-4729</link>
      <guid>https://forem.com/peaklinestudio/how-to-auto-generate-api-documentation-from-any-github-repo-without-writing-a-word-4729</guid>
      <description>&lt;p&gt;Good API documentation is one of those things everyone agrees matters and almost nobody keeps up to date.&lt;/p&gt;

&lt;p&gt;The README gets written during the launch sprint, then drifts from reality as endpoints change, parameters shift, and the intern who wrote the original docs has moved on.&lt;/p&gt;

&lt;p&gt;Here's a way to fix that without writing anything by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with manual docs
&lt;/h2&gt;

&lt;p&gt;The standard approach to API documentation looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write docs by hand during initial development&lt;/li&gt;
&lt;li&gt;Forget to update them when the API changes&lt;/li&gt;
&lt;li&gt;Get bug reports from users who followed the now-wrong docs&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The alternative — auto-generating docs from code — has existed for years (Swagger/OpenAPI, JSDoc, etc.), but it requires you to annotate your code first. Which is also a thing nobody keeps up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  A different approach: generate from the code itself
&lt;/h2&gt;

&lt;p&gt;What if you could point a tool at any GitHub repo and get back a full README, API reference, and usage examples — with no annotations required?&lt;/p&gt;

&lt;p&gt;That's what the &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;Code Documentation Generator API&lt;/a&gt; does. It fetches your repository, analyzes the actual code structure, and generates contextual documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The API has three endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/generate/full&lt;/code&gt;&lt;/strong&gt; — generates a complete README + API reference in one shot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/generate/readme&lt;/code&gt;&lt;/strong&gt; — README only (project overview, installation, usage, examples)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/generate/api-reference&lt;/code&gt;&lt;/strong&gt; — API reference only (endpoints, parameters, return types, error codes)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Basic usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://code-docs-api.peakline-ops.workers.dev/generate/readme"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "repo_url": "https://github.com/your-org/your-repo",
    "doc_type": "readme"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"documentation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"# Your Project

A REST API for..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-org/your-repo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"readme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cached"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results are cached for 24 hours, so regenerating docs on a CI run is fast after the first call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate it in CI
&lt;/h3&gt;

&lt;p&gt;The real value comes from wiring this into your deployment pipeline. There's a &lt;a href="https://github.com/peaklineops/code-docs-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; that does this automatically on every push to main:&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="c1"&gt;# .github/workflows/docs.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-generate docs&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;rapidapi_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPIDAPI_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;repo_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.repository&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;doc_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
          &lt;span class="na"&gt;output_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;README.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every merge to main triggers a fresh README. The Action auto-commits the updated file back to the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the generated docs look like
&lt;/h2&gt;

&lt;p&gt;For a typical REST API project, the output includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project overview&lt;/strong&gt; — what the service does, who it's for&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prerequisites and installation&lt;/strong&gt; — inferred from package.json, requirements.txt, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint reference&lt;/strong&gt; — routes detected from the codebase, with request/response examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; — detected from auth middleware patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error codes&lt;/strong&gt; — inferred from error handling logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examples&lt;/strong&gt; — working code samples in multiple languages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It won't replace hand-crafted narrative documentation for complex systems, but it's an excellent baseline that's always synchronized with the current code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;The API is available on RapidAPI with a free tier (5 requests/day, no credit card required):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;Subscribe on RapidAPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Grab your API key from the RapidAPI dashboard&lt;/li&gt;
&lt;li&gt;Make your first call with the curl above&lt;/li&gt;
&lt;li&gt;Set up the GitHub Action for continuous generation&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;What do you use for keeping docs in sync with your codebase? Curious if anyone has found a better workflow.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>api</category>
      <category>github</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Stop Writing README Files by Hand: Auto-Generate API Docs on Every Merge</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Mon, 30 Mar 2026 01:32:27 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/stop-writing-readme-files-by-hand-auto-generate-api-docs-on-every-merge-go5</link>
      <guid>https://forem.com/peaklinestudio/stop-writing-readme-files-by-hand-auto-generate-api-docs-on-every-merge-go5</guid>
      <description>&lt;p&gt;Every engineering team has the same README problem.&lt;/p&gt;

&lt;p&gt;Someone writes a great README when the project launches. Six months later, the API has changed in three places but the docs haven't. New developers onboard with incorrect examples. API consumers hit endpoints that no longer exist. And nobody wants to be the person who has to sit down and manually rewrite the documentation.&lt;/p&gt;

&lt;p&gt;The traditional answer is "just update the docs when you change the code." The actual behavior is: docs drift.&lt;/p&gt;

&lt;p&gt;There's a better approach: treat documentation as a build artifact, not a manual deliverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern: Docs as CI Output
&lt;/h2&gt;

&lt;p&gt;The same way you run tests on every PR and generate coverage reports on every merge, you can generate documentation automatically. The key insight is that your codebase already contains everything needed to produce accurate docs — function signatures, module structure, exports, route handlers. An AI model can read that structure and produce documentation that reflects what the code actually does, not what someone wrote about it six months ago.&lt;/p&gt;

&lt;p&gt;Here's how to wire this up in a GitHub Actions workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Two Files, Five Minutes
&lt;/h2&gt;

&lt;p&gt;You'll need a RapidAPI key with access to the &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;Code Documentation Generator API&lt;/a&gt;. The free tier handles small repos comfortably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Add your RapidAPI key as a secret&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your repo: Settings → Secrets and variables → Actions → New repository secret&lt;/p&gt;

&lt;p&gt;Name: &lt;code&gt;RAPID_API_KEY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Create &lt;code&gt;.github/workflows/docs.yml&lt;/code&gt;&lt;/strong&gt;&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate Documentation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generate-docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate full documentation&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repo_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/${{ github.repository }}&lt;/span&gt;
          &lt;span class="na"&gt;doc_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
          &lt;span class="na"&gt;output_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/API.md&lt;/span&gt;
          &lt;span class="na"&gt;rapid_api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPID_API_KEY }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit updated docs&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name "github-actions[bot]"&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email "github-actions[bot]@users.noreply.github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git add docs/API.md&lt;/span&gt;
          &lt;span class="s"&gt;git diff --staged --quiet || git commit -m "docs: auto-update API documentation [skip ci]"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Every time you push to main, the action:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches your repo's source files&lt;/li&gt;
&lt;li&gt;Sends them to the documentation API&lt;/li&gt;
&lt;li&gt;Gets back structured markdown docs&lt;/li&gt;
&lt;li&gt;Commits the result back to your repo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;[skip ci]&lt;/code&gt; tag prevents the commit from triggering another docs run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Doc Types
&lt;/h2&gt;

&lt;p&gt;The action supports three modes via the &lt;code&gt;doc_type&lt;/code&gt; parameter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;readme&lt;/code&gt;&lt;/strong&gt; — Generates a full project README: what it does, installation, usage, examples. Good for public repos where the README is the first thing people read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;api-reference&lt;/code&gt;&lt;/strong&gt; — Generates a structured API reference: all endpoints/functions with parameters, return types, and examples. Good for libraries and REST APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;full&lt;/code&gt;&lt;/strong&gt; — Combines both. A complete README plus detailed API reference in a single document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selective Documentation
&lt;/h2&gt;

&lt;p&gt;If you only want to regenerate docs when source files change (not on every push), use path filtering:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;src/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lib/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.ts'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.js'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.py'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This skips the doc generation step when only tests, config files, or existing docs change — no unnecessary API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting README from API Reference
&lt;/h2&gt;

&lt;p&gt;For larger projects, you may want to keep your README human-authored (for tone and positioning) but auto-generate the technical API reference:&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;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate API reference only&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repo_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/${{ github.repository }}&lt;/span&gt;
          &lt;span class="na"&gt;doc_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-reference&lt;/span&gt;
          &lt;span class="na"&gt;output_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/API_REFERENCE.md&lt;/span&gt;
          &lt;span class="na"&gt;rapid_api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPID_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a consistently-updated &lt;code&gt;docs/API_REFERENCE.md&lt;/code&gt; while leaving your &lt;code&gt;README.md&lt;/code&gt; under human control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;After the first run, you'll have a &lt;code&gt;docs/API.md&lt;/code&gt; (or wherever you pointed &lt;code&gt;output_file&lt;/code&gt;) that reflects the current state of your codebase. It updates automatically on every merge to main. New team members get accurate docs on day one. API consumers don't hit deprecated endpoints.&lt;/p&gt;

&lt;p&gt;The documentation debt stops accumulating.&lt;/p&gt;




&lt;p&gt;The Code Documentation Generator API is available on &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;RapidAPI&lt;/a&gt;. The GitHub Action is at &lt;a href="https://github.com/peaklineops/code-docs-action" rel="noopener noreferrer"&gt;peaklineops/code-docs-action&lt;/a&gt;. Free tier: 5 requests/day. Pro: $9/mo for 100 requests/day.&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>cicd</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Debug GitHub Actions Webhooks Without ngrok</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Mon, 30 Mar 2026 01:23:32 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/debug-github-actions-webhooks-without-ngrok-dl7</link>
      <guid>https://forem.com/peaklinestudio/debug-github-actions-webhooks-without-ngrok-dl7</guid>
      <description>&lt;p&gt;If you've ever tried to debug a webhook integration inside a GitHub Actions workflow, you know the pain.&lt;/p&gt;

&lt;p&gt;Your CI pipeline fires a webhook to an external service. Something goes wrong. You have no idea what payload actually got sent — GitHub's UI shows you the trigger, not what your workflow delivered downstream.&lt;/p&gt;

&lt;p&gt;The usual answer is ngrok: spin up a tunnel, expose a local server, redirect your webhook, inspect it manually. It works, but it's tedious, stateful, and dies the moment your laptop sleeps.&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem in Detail
&lt;/h2&gt;

&lt;p&gt;Say you have a workflow like this:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Notify on Deploy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Send deploy webhook&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -X POST ${{ secrets.WEBHOOK_URL }} \&lt;/span&gt;
            &lt;span class="s"&gt;-H "Content-Type: application/json" \&lt;/span&gt;
            &lt;span class="s"&gt;-d '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "actor": "${{ github.actor }}"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this breaks, you want to see the exact payload that was sent — headers, body, response code. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub shows you workflow logs, not what the receiving server saw&lt;/li&gt;
&lt;li&gt;If your target URL is broken or returns a 500, you can't see what it received&lt;/li&gt;
&lt;li&gt;Replay is impossible without re-triggering the whole pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using a Webhook Inspection Endpoint
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hooktest.peakline-ops.workers.dev" rel="noopener noreferrer"&gt;HookTest&lt;/a&gt; lets you create a disposable HTTPS endpoint that captures every request — headers, body, method, timestamp. No install, no local server, no tunnels.&lt;/p&gt;

&lt;p&gt;Here's how to wire it into the workflow above:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create a test endpoint&lt;/strong&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;-X&lt;/span&gt; POST https://hooktest.peakline-ops.workers.dev/api/endpoints &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"label": "github-actions-debug"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://hooktest.peakline-ops.workers.dev/w/abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github-actions-debug"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Point your test run at it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Temporarily swap your &lt;code&gt;WEBHOOK_URL&lt;/code&gt; secret for the HookTest endpoint URL, or add a parallel debug step:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Debug webhook payload&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;env.DEBUG_WEBHOOKS == 'true'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -X POST https://hooktest.peakline-ops.workers.dev/w/abc123 \&lt;/span&gt;
            &lt;span class="s"&gt;-H "Content-Type: application/json" \&lt;/span&gt;
            &lt;span class="s"&gt;-H "X-GitHub-Run-ID: ${{ github.run_id }}" \&lt;/span&gt;
            &lt;span class="s"&gt;-d '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "actor": "${{ github.actor }}"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;DEBUG_WEBHOOKS=true&lt;/code&gt; as a repo variable when debugging, &lt;code&gt;false&lt;/code&gt; in production. Zero secrets exposed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Inspect the captured request&lt;/strong&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 https://hooktest.peakline-ops.workers.dev/api/endpoints/abc123/requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get back everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"req_xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"x-github-run-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345678"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ref&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refs/heads/main&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sha&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;a1b2c3d4&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;actor&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;octocat&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"captured_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-29T10:23:45Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Replay to the real target when ready&lt;/strong&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;-X&lt;/span&gt; POST https://hooktest.peakline-ops.workers.dev/api/requests/req_xyz/replay &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"target_url": "https://your-real-service.com/webhook"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replay lets you test your receiving service without re-triggering the entire CI pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard View
&lt;/h2&gt;

&lt;p&gt;If you prefer a visual interface, the &lt;a href="https://hooktest.peakline-ops.workers.dev" rel="noopener noreferrer"&gt;HookTest dashboard&lt;/a&gt; gives you a real-time view of all captured requests with full header and body inspection. No account required for the free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro Tips for CI/CD Webhook Debugging
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Add a GitHub run ID header to every webhook&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Always include &lt;code&gt;X-GitHub-Run-ID: ${{ github.run_id }}&lt;/code&gt; in your outgoing webhooks. This lets you correlate captured payloads back to specific pipeline runs in the GitHub UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Test the receiving service in isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Capture your payload once with HookTest, then replay it repeatedly against your service as you iterate — no need to keep pushing commits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use transform rules to sanitize before forwarding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HookTest's Pro tier supports transform rules: mask sensitive fields (like tokens), rename fields for compatibility, or filter out requests by header value. Useful when your payload contains secrets you don't want stored in plain text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Temporary endpoints are free&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free tier endpoints auto-expire after 24 hours. For debugging a CI pipeline, that's usually enough. Pro endpoints persist indefinitely, which is useful for long-running test environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Log the Payload in the Workflow?
&lt;/h2&gt;

&lt;p&gt;You can — and you should — add &lt;code&gt;echo&lt;/code&gt; statements or &lt;code&gt;cat&lt;/code&gt; the payload in your workflow logs. But that only shows you what you sent. It doesn't show you what your receiving server saw (after any proxies, load balancers, or CDN rewrites), what headers arrived, or how the response looked. An inspection endpoint captures the full inbound request as the server sees it.&lt;/p&gt;




&lt;p&gt;HookTest is free to use at &lt;a href="https://hooktest.peakline-ops.workers.dev" rel="noopener noreferrer"&gt;hooktest.peakline-ops.workers.dev&lt;/a&gt;. Free tier: 3 endpoints, 24-hour expiry, 100 requests per endpoint. Pro ($9/mo): 10 persistent endpoints, transform rules, unlimited requests.&lt;/p&gt;

&lt;p&gt;Happy debugging.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>github</category>
      <category>devtools</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Keep Your GitHub Repo Docs Always Up-to-Date with This Free GitHub Action</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Sun, 29 Mar 2026 16:41:37 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/keep-your-github-repo-docs-always-up-to-date-with-this-free-github-action-2n6e</link>
      <guid>https://forem.com/peaklinestudio/keep-your-github-repo-docs-always-up-to-date-with-this-free-github-action-2n6e</guid>
      <description>&lt;p&gt;We've all been there: you ship a feature, update the code, and then... the README still describes the old behavior. Three weeks later someone files an issue asking why the docs don't match. You fix the docs, ship another feature, and the cycle repeats.&lt;/p&gt;

&lt;p&gt;Documentation drift is a solved problem — if you automate it.&lt;/p&gt;

&lt;p&gt;In this tutorial I'll walk you through setting up a GitHub Action that automatically regenerates your README (or full API docs) every time you push to main. Zero config, one YAML file, done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/peaklineops/code-docs-action" rel="noopener noreferrer"&gt;code-docs-action&lt;/a&gt; calls the &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;Code Documentation Generator API&lt;/a&gt; on every push. The API analyzes your repository's source files using Claude AI and returns structured markdown documentation. The action then writes it to your target file and commits it back.&lt;/p&gt;

&lt;p&gt;The API has a free tier (5 scans/month) — plenty for a side project with infrequent pushes, or for testing before upgrading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get a Free RapidAPI Key
&lt;/h2&gt;

&lt;p&gt;Sign up at &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;RapidAPI&lt;/a&gt; and subscribe to the free BASIC plan. Copy your &lt;code&gt;X-RapidAPI-Key&lt;/code&gt; from the API playground.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add the Secret to Your Repo
&lt;/h2&gt;

&lt;p&gt;In your GitHub repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Secrets and variables → Actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New repository secret&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;RAPIDAPI_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Value: your RapidAPI key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Create the Workflow File
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/docs.yml&lt;/code&gt; in your repo:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-generate documentation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# lets you trigger manually too&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generate-docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;  &lt;span class="c1"&gt;# needed to commit back to repo&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate README&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;rapidapi-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPIDAPI_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;doc-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;readme&lt;/span&gt;        &lt;span class="c1"&gt;# or "full" or "api-reference"&lt;/span&gt;
          &lt;span class="na"&gt;output-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;README.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push — the action runs immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Gets Generated
&lt;/h2&gt;

&lt;p&gt;The API reads your source files (ignoring &lt;code&gt;node_modules&lt;/code&gt;, build artifacts, etc.) and generates documentation tailored to your codebase. For a typical project you'll get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project overview&lt;/strong&gt; — what the project does, based on code structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installation instructions&lt;/strong&gt; — inferred from &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage examples&lt;/strong&gt; — based on exported functions and public APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API reference&lt;/strong&gt; — for libraries, a full table of exports with descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Options
&lt;/h2&gt;

&lt;p&gt;The action has several inputs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Default&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;doc-type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;readme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;readme&lt;/code&gt;, &lt;code&gt;api-reference&lt;/code&gt;, or &lt;code&gt;full&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output-file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where to write the output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;commit-docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;false&lt;/code&gt; to just write the file (useful in PR checks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;commit-message&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs: auto-update documentation [skip ci]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The commit message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Generating API Docs Separately
&lt;/h2&gt;

&lt;p&gt;If you want a separate API reference file in addition to your README:&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;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate README&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1.1&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rapidapi-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPIDAPI_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;doc-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;readme&lt;/span&gt;
      &lt;span class="na"&gt;output-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;README.md&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate API Reference&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1.1&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rapidapi-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPIDAPI_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;doc-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-reference&lt;/span&gt;
      &lt;span class="na"&gt;output-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/API.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tip: Run on PRs Without Committing
&lt;/h2&gt;

&lt;p&gt;For pull requests, you might want to preview generated docs without auto-committing:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preview-docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview docs changes&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaklineops/code-docs-action@v1.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;rapidapi-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RAPIDAPI_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;commit-docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;   &lt;span class="c1"&gt;# generate only, don't commit&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Show diff&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git diff README.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets reviewers see exactly how the docs will change before merging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Once this is set up, documentation becomes a side effect of shipping code rather than a separate task. Your README reflects the current state of the codebase, always.&lt;/p&gt;

&lt;p&gt;The action is open source: &lt;a href="https://github.com/peaklineops/code-docs-action" rel="noopener noreferrer"&gt;peaklineops/code-docs-action&lt;/a&gt;. Issues and PRs welcome.&lt;/p&gt;

&lt;p&gt;The underlying API is on RapidAPI: &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;Code Documentation Generator&lt;/a&gt; — free tier available, no credit card required.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>documentation</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Filter, Mask, and Route Stripe Webhooks Without Writing Code</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Sun, 29 Mar 2026 16:21:16 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/how-to-filter-mask-and-route-stripe-webhooks-without-writing-code-2jb8</link>
      <guid>https://forem.com/peaklinestudio/how-to-filter-mask-and-route-stripe-webhooks-without-writing-code-2jb8</guid>
      <description>&lt;p&gt;If you've ever built a Stripe integration, you know the pain: every webhook event hits your endpoint — even the ones you don't care about. &lt;code&gt;charge.updated&lt;/code&gt; flooding your logs while you're only watching for &lt;code&gt;payment_intent.succeeded&lt;/code&gt;. Sensitive card metadata sitting in plain text in your inspection logs. Fields named one thing in Stripe but something different in your database schema.&lt;/p&gt;

&lt;p&gt;Normally, solving this means writing middleware. A filter here, a field mask there, a rename transformer somewhere else. It's boilerplate — but it has to be written, tested, and maintained.&lt;/p&gt;

&lt;p&gt;I built a small feature into &lt;a href="https://hooktest-landing.pages.dev" rel="noopener noreferrer"&gt;HookTest&lt;/a&gt; (a webhook testing and inspection tool) called &lt;strong&gt;Transform Rules&lt;/strong&gt; that handles all three cases without writing any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Transform Rules Do
&lt;/h2&gt;

&lt;p&gt;When a webhook hits your HookTest endpoint, before the request is stored, you can apply a chain of rules in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt; — only capture requests where a JSON field matches a condition (equals, contains, starts with). Anything that doesn't match is acknowledged with 200 OK but silently dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mask&lt;/strong&gt; — replace a sensitive field value with a placeholder (&lt;code&gt;***&lt;/code&gt; by default). The payload is stored with the masked value — the original never touches disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rename&lt;/strong&gt; — rename a JSON key before storage (e.g., &lt;code&gt;user_id&lt;/code&gt; → &lt;code&gt;userId&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rules run in the order you define them. Filter rules always run first (even if you add them later), so a dropped request never gets transformed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Example: Filtering Stripe Events
&lt;/h2&gt;

&lt;p&gt;Say you're testing a checkout flow and you only want to see &lt;code&gt;checkout.session.completed&lt;/code&gt; events — nothing else.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a HookTest endpoint and copy the URL into your Stripe webhook dashboard.&lt;/li&gt;
&lt;li&gt;Open the "Transform Rules" tab on your endpoint.&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;Filter&lt;/strong&gt; rule: path = &lt;code&gt;type&lt;/code&gt;, operator = &lt;code&gt;eq&lt;/code&gt;, value = &lt;code&gt;checkout.session.completed&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now when Stripe fires &lt;code&gt;payment_intent.created&lt;/code&gt;, &lt;code&gt;charge.updated&lt;/code&gt;, or any other event, HookTest acknowledges it silently. Your inspection log stays clean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;gets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stored:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout.session.completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cs_test_..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Everything&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;acknowledged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dropped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Masking Sensitive Data
&lt;/h2&gt;

&lt;p&gt;Stripe payloads can contain customer emails, card last-four digits, billing addresses. If you're sharing your HookTest endpoint URL with a teammate to debug something, you probably don't want that data in the captured log.&lt;/p&gt;

&lt;p&gt;Add a &lt;strong&gt;Mask&lt;/strong&gt; rule: path = &lt;code&gt;data.object.customer_details.email&lt;/code&gt;, mask = &lt;code&gt;[REDACTED]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The stored payload looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"customer_details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Smith"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw email never gets written to storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Write a Filter in Your App?
&lt;/h2&gt;

&lt;p&gt;You can — but there's a catch. When you're &lt;strong&gt;testing&lt;/strong&gt;, you want to inspect what Stripe is actually sending before your app logic runs. If your filter middleware drops the event, you've lost visibility into the raw payload.&lt;/p&gt;

&lt;p&gt;HookTest sits upstream of your app. It captures first, applies your rules, then you can see exactly what would have been stored — and replay it to your real endpoint when you're ready.&lt;/p&gt;

&lt;p&gt;The replay feature is especially useful here: capture once, inspect, then fire it at &lt;code&gt;http://localhost:3000/webhooks&lt;/code&gt; as many times as you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Availability
&lt;/h2&gt;

&lt;p&gt;Transform Rules are a Pro feature ($9/mo). Free tier gets unlimited basic capture and inspection with no rules.&lt;/p&gt;

&lt;p&gt;If you're testing Stripe, GitHub, Twilio, or any other webhook source, &lt;a href="https://hooktest-landing.pages.dev" rel="noopener noreferrer"&gt;give HookTest a try&lt;/a&gt; — the free tier should cover most debugging sessions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;HookTest is built on Cloudflare Workers + D1 with a zero-dependency dashboard. The transform rule engine is ~120 lines of TypeScript.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>stripe</category>
      <category>devtools</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Test Stripe Webhooks Without Deploying Code</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Sat, 28 Mar 2026 17:22:29 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/how-to-test-stripe-webhooks-without-deploying-code-132n</link>
      <guid>https://forem.com/peaklinestudio/how-to-test-stripe-webhooks-without-deploying-code-132n</guid>
      <description>&lt;p&gt;You're integrating Stripe. You need to handle &lt;code&gt;payment_intent.succeeded&lt;/code&gt;. Before you write a single line of handler code, you need to know exactly what Stripe sends.&lt;/p&gt;

&lt;p&gt;The classic approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy a temporary endpoint with logging&lt;/li&gt;
&lt;li&gt;Configure it in Stripe Dashboard&lt;/li&gt;
&lt;li&gt;Trigger a test event&lt;/li&gt;
&lt;li&gt;Dig through logs&lt;/li&gt;
&lt;li&gt;Tweak your code, repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's 15 minutes of plumbing before you write any real logic. Every time you need to check a different event type, you do it again.&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fast approach: use a webhook inspector
&lt;/h2&gt;

&lt;p&gt;A webhook inspector gives you an instant HTTPS URL. Point your webhook provider at it, trigger an event, and see the full payload — headers, body, timing — immediately in your browser. No deploy, no logging setup, no waiting.&lt;/p&gt;

&lt;p&gt;For this walkthrough I'll use &lt;a href="https://hooktest-landing.pages.dev" rel="noopener noreferrer"&gt;HookTest&lt;/a&gt; — a free tool I built for exactly this workflow. No account required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Get your endpoint URL
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://hooktest.peakline-ops.workers.dev" rel="noopener noreferrer"&gt;HookTest&lt;/a&gt; and click &lt;strong&gt;New Endpoint&lt;/strong&gt;. You'll get a URL like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://hooktest.peakline-ops.workers.dev/w/abc123xyz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your endpoint is live.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Add it to Stripe
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Stripe Dashboard → Developers → Webhooks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add endpoint&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste your HookTest URL&lt;/li&gt;
&lt;li&gt;Select the events you care about — or just pick &lt;strong&gt;All events&lt;/strong&gt; for exploration&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add endpoint&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now trigger a test event. In Stripe, find the webhook you just created and click &lt;strong&gt;Send test webhook&lt;/strong&gt;. Pick &lt;code&gt;payment_intent.succeeded&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: See what Stripe actually sends
&lt;/h2&gt;

&lt;p&gt;Switch back to HookTest. The request appears immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /w/abc123xyz
stripe-signature: t=1711641600,v1=3c5d8f...
content-type: application/json

{
  "id": "evt_3OqABC2eZvKYlo2C1711641600",
  "object": "event",
  "api_version": "2024-04-10",
  "created": 1711641600,
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_3OqABC2eZvKYlo2C1711641600",
      "object": "payment_intent",
      "amount": 2999,
      "amount_received": 2999,
      "currency": "usd",
      "status": "succeeded",
      "customer": "cus_ABC123",
      "metadata": {},
      "payment_method": "pm_1OqABC2eZvKYlo2C..."
    }
  },
  "livemode": false
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you know exactly what fields exist and what types they are. Write your handler with confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Replay to your local dev server
&lt;/h2&gt;

&lt;p&gt;Once your handler is written and running on &lt;code&gt;localhost:3000&lt;/code&gt;, you don't need to re-trigger the event in Stripe. Click &lt;strong&gt;Replay&lt;/strong&gt; on the captured request, enter your local URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000/webhooks/stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HookTest re-sends the exact payload with all original headers — including the &lt;code&gt;Stripe-Signature&lt;/code&gt; header. Your local handler sees a real request.&lt;/p&gt;

&lt;p&gt;This is the part that saves the most time. Iterate on your handler by replaying the same payload over and over, without touching Stripe each time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What about ngrok?
&lt;/h2&gt;

&lt;p&gt;ngrok tunnels traffic to your local server — useful when you want Stripe to hit localhost directly. But that means your server needs to be running and accessible before you can see anything.&lt;/p&gt;

&lt;p&gt;Webhook inspection flips this: capture first, understand the payload, &lt;em&gt;then&lt;/em&gt; write the handler. Use both tools for different stages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exploring payload structure&lt;/td&gt;
&lt;td&gt;HookTest (inspect)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iterating on your handler&lt;/td&gt;
&lt;td&gt;HookTest (replay)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end integration test&lt;/td&gt;
&lt;td&gt;ngrok / &lt;code&gt;stripe listen&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Stripe-specific tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Verify signatures in your handler.&lt;/strong&gt; Every Stripe webhook includes a &lt;code&gt;Stripe-Signature&lt;/code&gt; header. Your handler should validate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Webhook Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_intent.succeeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// fulfill order&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;&lt;strong&gt;Note&lt;/strong&gt;: When replaying from HookTest, the signature won't verify unless you disable signature checking in dev mode — that's fine for development. In production, always verify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch for event versions.&lt;/strong&gt; Stripe sends &lt;code&gt;api_version&lt;/code&gt; in every event. If your Stripe account is on a different API version than what you're testing, the payload shape can differ. Check this in your Stripe Dashboard under Developers → API keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other webhook providers
&lt;/h2&gt;

&lt;p&gt;This exact workflow works with any webhook-based API:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Where to configure&lt;/th&gt;
&lt;th&gt;Test event trigger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Settings → Webhooks&lt;/td&gt;
&lt;td&gt;"Redeliver" button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shopify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partners → App → Webhooks&lt;/td&gt;
&lt;td&gt;Test notification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twilio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Console → Phone Numbers&lt;/td&gt;
&lt;td&gt;Use ngrok + real SMS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;App config → Event Subscriptions&lt;/td&gt;
&lt;td&gt;Slash command or action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SendGrid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Settings → Mail Settings → Event Webhook&lt;/td&gt;
&lt;td&gt;Send a test email&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The next time you need to integrate a webhook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open HookTest → get an endpoint URL (5 seconds, no account)&lt;/li&gt;
&lt;li&gt;Paste it into your provider's webhook config&lt;/li&gt;
&lt;li&gt;Trigger a test event → see the full payload immediately&lt;/li&gt;
&lt;li&gt;Write your handler with real field names and types&lt;/li&gt;
&lt;li&gt;Replay from HookTest → iterate without re-triggering the source&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No deploy cycle. No log parsing. No guessing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hooktest.peakline-ops.workers.dev" rel="noopener noreferrer"&gt;Try HookTest free →&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built HookTest because I was tired of re-deploying logging endpoints every time I integrated a new webhook provider. The free tier is genuinely useful — no account, no credit card, 100 requests before anything expires.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>productivity</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How I Built an AI That Generates README Files and API Docs from Any GitHub Repo</title>
      <dc:creator>Peakline Studio</dc:creator>
      <pubDate>Sat, 28 Mar 2026 15:31:59 +0000</pubDate>
      <link>https://forem.com/peaklinestudio/how-i-built-an-ai-that-generates-readme-files-and-api-docs-from-any-github-repo-400f</link>
      <guid>https://forem.com/peaklinestudio/how-i-built-an-ai-that-generates-readme-files-and-api-docs-from-any-github-repo-400f</guid>
      <description>&lt;p&gt;Every developer has been there: you've built something great, but writing the documentation feels like a second project. README files go stale, API references never get written, and onboarding new contributors is painful.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Code Documentation Generator&lt;/strong&gt; to solve this — a single POST request that returns production-ready Markdown documentation for any public GitHub repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Send a GitHub repo URL, get back structured Markdown documentation. That's it.&lt;/p&gt;

&lt;p&gt;Three output formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full documentation&lt;/strong&gt; — README + API reference + usage examples, all in one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README only&lt;/strong&gt; — project overview, installation, usage, contributing guide&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API reference&lt;/strong&gt; — every function/class/method documented with parameters and return types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It uses Claude AI under the hood to actually understand your code, not just parse it. So the output reads like a human wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API (one request)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://code-docs-api.peakline-ops.workers.dev/v1/generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "repo_url": "https://github.com/your-org/your-repo",
    "ref": "main"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-org/your-repo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"documentation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"# Your Project&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration. Pipe the &lt;code&gt;documentation&lt;/code&gt; field wherever you need it — your wiki, your CMS, a GitHub Action that commits docs on every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the repo tree from GitHub's API&lt;/li&gt;
&lt;li&gt;Reads source files (filtering out node_modules, build artifacts, etc.)&lt;/li&gt;
&lt;li&gt;Sends code + structure to Claude Sonnet with a format-specific prompt&lt;/li&gt;
&lt;li&gt;Returns structured Markdown&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything runs on Cloudflare Workers — globally distributed, sub-100ms cold starts, no servers to maintain.&lt;/p&gt;

&lt;p&gt;Results are cached in D1 (Cloudflare's edge SQLite) for 24 hours, so repeated requests for the same repo/ref are instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases I've seen
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Keep docs in sync with code&lt;/strong&gt; — run it in CI on every merge to main, commit the output to your repo. No more stale READMEs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboard contributors faster&lt;/strong&gt; — new devs can generate up-to-date docs for any unfamiliar module before diving in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document OSS dependencies&lt;/strong&gt; — sometimes a library you depend on has terrible docs. Generate your own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API documentation for internal tools&lt;/strong&gt; — your company's internal utilities deserve docs too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Available on RapidAPI
&lt;/h2&gt;

&lt;p&gt;The API is now listed on RapidAPI: &lt;strong&gt;Code Documentation Generator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free tier: 5 requests/month to try it out&lt;br&gt;
Pro ($9/mo): for regular use&lt;br&gt;
Ultra ($29/mo): for teams and CI pipelines&lt;/p&gt;

&lt;p&gt;Direct link: &lt;a href="https://rapidapi.com/peaklineops/api/code-documentation-generator" rel="noopener noreferrer"&gt;https://rapidapi.com/peaklineops/api/code-documentation-generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or hit the endpoint directly — no account needed for the free tier (IP-based, 5 requests/month).&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Action wrapper (document your repo on every push with zero config)&lt;/li&gt;
&lt;li&gt;Support for private repos via GitHub token&lt;/li&gt;
&lt;li&gt;Webhook trigger (push to a branch → docs auto-update)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would love feedback from anyone who tries it. What formats are most useful? What's missing?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Cloudflare Workers + D1 + Claude API. Stack is ~300 lines of TypeScript.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aigithubopensource</category>
    </item>
  </channel>
</rss>
