<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ghostly Inc. Dev Blog]]></title><description><![CDATA[Practical guides, release notes, and behind-the-scenes notes for Ghostly Inc. tools. Built for developers who value simple workflows, privacy, and control.]]></description><link>https://ghostlyinc.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69acb58786766ac3a6f41781/34fa54ff-c1c0-4b36-b707-9bb50dc9ebec.png</url><title>Ghostly Inc. Dev Blog</title><link>https://ghostlyinc.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 03 Nov 2026 04:29:09 GMT</lastBuildDate><atom:link href="https://ghostlyinc.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Test Webhooks Locally Without Deploying]]></title><description><![CDATA[Forward real webhook requests to localhost through a temporary HTTPS link, then close it when the test is done.]]></description><link>https://ghostlyinc.hashnode.dev/how-to-test-webhooks-locally-without-deploying</link><guid isPermaLink="true">https://ghostlyinc.hashnode.dev/how-to-test-webhooks-locally-without-deploying</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Testing]]></category><category><![CDATA[webhooks]]></category><category><![CDATA[localhost]]></category><category><![CDATA[Developer Tools]]></category><dc:creator><![CDATA[Ghostly]]></dc:creator><pubDate>Thu, 22 Oct 2026 23:25:32 GMT</pubDate><content:encoded><![CDATA[<p>A webhook provider cannot call <code>localhost</code>. Your application can be working perfectly on your machine, but from the provider's point of view, <code>http://localhost:3000</code> points back to its own infrastructure.</p>
<p>The usual workaround is to deploy every change before testing. That is slow when you are still adjusting payload parsing, signatures, retries, or response codes. A temporary HTTPS link gives you a shorter feedback loop: the provider sends a real request to a public URL, and the request is forwarded to your local server.</p>
<p>This guide builds a small receiver, exposes it only for the test, and closes the link afterward.</p>
<h2>1. Create a local webhook receiver</h2>
<p>Here is a minimal Express endpoint for inspecting JSON payloads:</p>
<pre><code class="language-javascript">const express = require("express");

const app = express();
app.use(express.json());

app.post("/webhooks/demo", (req, res) =&gt; {
  console.log("Headers:", req.headers);
  console.log("Payload:", req.body);
  res.sendStatus(204);
});

app.listen(3000, () =&gt; {
  console.log("Webhook receiver: http://localhost:3000/webhooks/demo");
});
</code></pre>
<p>Install the dependency and start the server:</p>
<pre><code class="language-bash">npm install express
node server.js
</code></pre>
<p>Before involving an external service, verify the route locally:</p>
<pre><code class="language-bash">curl -i -X POST http://localhost:3000/webhooks/demo \
  -H "Content-Type: application/json" \
  -d '{"event":"test","message":"hello from localhost"}'
</code></pre>
<p>You should receive <code>HTTP/1.1 204 No Content</code>, and the payload should appear in the terminal running the server.</p>
<blockquote>
<p>This receiver is intentionally simple. Production webhook handlers should authenticate requests and usually need the unmodified request body for signature verification. Follow the provider's documentation before using the endpoint with real data.</p>
</blockquote>
<h2>2. Create a temporary public HTTPS link</h2>
<p>Open Ghostly Share while the local server is running. It should discover the app on port <code>3000</code> automatically. Select the app, start a <strong>Public</strong> share, and copy the generated HTTPS URL.</p>
<p>If you prefer the terminal, the equivalent CLI workflow is:</p>
<pre><code class="language-bash">ghs share 3000 --expires-after 30m --url-only
</code></pre>
<p>Keep the desktop app or CLI process running for the duration of the test. The public URL forwards requests to the local origin; it does not deploy or copy the application.</p>
<p>Append the local route to the generated host:</p>
<pre><code class="language-text">https://&lt;temporary-host&gt;/webhooks/demo
</code></pre>
<p>That complete URL is the callback address you enter in the webhook provider's test or sandbox configuration.</p>
<h2>3. Send a real test event</h2>
<p>Use the provider's <strong>Send test event</strong>, <strong>Redeliver</strong>, or equivalent action. Watch the local terminal for the headers and JSON payload, and check the delivery log on the provider side.</p>
<p>A successful round trip should look like this:</p>
<ol>
<li>The provider sends an HTTPS request to the temporary URL.</li>
<li>The request reaches your local <code>/webhooks/demo</code> route.</li>
<li>Your handler logs the payload and returns <code>204</code>.</li>
<li>The provider records a successful delivery.</li>
</ol>
<p>You can now change the handler and send another event immediately. There is no deployment step between iterations.</p>
<h2>Common problems</h2>
<h3>The provider reports 404</h3>
<p>Confirm that the public callback includes the complete local path. Sharing port <code>3000</code> does not automatically add <code>/webhooks/demo</code>.</p>
<h3>The provider times out</h3>
<p>Return a successful response quickly, then move slower work to a queue or background process. Many providers retry when a handler takes too long.</p>
<h3>The event arrives more than once</h3>
<p>Webhook delivery is commonly at least once. Store the provider's event ID and make the real handler idempotent instead of assuming every request is unique.</p>
<h3>Signature verification fails</h3>
<p>Many signature schemes require the exact raw request body. JSON parsing can change what gets verified, so use the provider's official middleware or raw-body instructions.</p>
<h2>Keep the test endpoint small and temporary</h2>
<p>A public webhook callback must be reachable without an interactive login page. That means password protection at the tunnel level is often unsuitable for automated providers. Protect the workflow in other ways:</p>
<ul>
<li>use a dedicated test account and test signing secret;</li>
<li>verify the provider's signature in your handler;</li>
<li>expose only the development service you need, not a database or admin panel;</li>
<li>set a short expiration time when possible;</li>
<li>avoid real customer data and production credentials;</li>
<li>stop the public share as soon as the test is complete.</li>
</ul>
<p>When you stop sharing or press <code>Ctrl+C</code>, the temporary route no longer serves your local application.</p>
<h2>A faster webhook feedback loop</h2>
<p>The useful part of local webhook testing is not the tunnel itself. It is the ability to inspect a real request, adjust your code, and retry within seconds. Temporary links keep that loop short without turning an unfinished handler into a permanent deployment.</p>
<p>Ghostly Share is available as a desktop app for Windows and Linux, with a separate CLI for terminal workflows:</p>
<ul>
<li><a href="https://github.com/Nix1983/GhostlyShare-Releases/releases/latest">Download the latest release</a></li>
<li><a href="https://apps.microsoft.com/detail/9PJ6DBC342GR">Install from the Microsoft Store</a></li>
<li><a href="https://github.com/Nix1983/GhostlyShare-Releases/blob/master/wiki/Home.md">Read the user guide</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Share Localhost Without Port Forwarding]]></title><description><![CDATA[Share a local web app over LAN or through a temporary public link with Ghostly Share, without deploying or changing router settings.]]></description><link>https://ghostlyinc.hashnode.dev/how-to-share-localhost-without-port-forwarding</link><guid isPermaLink="true">https://ghostlyinc.hashnode.dev/how-to-share-localhost-without-port-forwarding</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[localhost]]></category><category><![CDATA[networking]]></category><category><![CDATA[cloudflare]]></category><dc:creator><![CDATA[Ghostly]]></dc:creator><pubDate>Wed, 21 Oct 2026 17:13:50 GMT</pubDate><content:encoded><![CDATA[<p>A local development server is easy to open on the computer where it is running. The moment you want to test it on a phone, show it to someone remotely, or receive a webhook, things become less convenient.</p>
<p>The usual alternatives include changing router settings, deploying an unfinished build, or manually configuring a tunnel. All of them add work that has little to do with the feature you are building.</p>
<p>Ghostly Share takes a simpler approach: it discovers local web apps and lets you share them either on your trusted local network or through a temporary public link.</p>
<h2>Choose the right kind of link</h2>
<p>Ghostly Share offers two independent sharing modes. You can use either one, or keep both active for the same local app when your workflow needs it.</p>
<h3>LAN sharing</h3>
<p>Use LAN sharing when the other device is connected to the same trusted network. It is a good fit for:</p>
<ul>
<li>testing a responisve site on a phone or tablet;</li>
<li>opening a dashboard on another computer;</li>
<li>checking a web interface on a TV or embedded device;</li>
<li>keeping a test completely inside your network.</li>
</ul>
<p>LAN sharing does not require Cloudflare or an internet connection. The link is intentionally not password-protected, so only use it on a network you trust.</p>
<h3>Public sharing</h3>
<p>Use a public link when the other person or service is somewhere else. Typical examples include:</p>
<ul>
<li>sending a work-in-progress demo to a client;</li>
<li>testing a webhook callback;</li>
<li>sharing a preview with a remote teammate;</li>
<li>opening a local app while away from the development machine.</li>
</ul>
<p>A temporary public link does not require a Ghostly Share account, a domain, or an API token. Anyone who receives the URL may try to open it, so enable password protection or an expiration time when appropriate.</p>
<h2>Share a local app from the desktop</h2>
<ol>
<li>Start your local website, API, dashboard, or development server.</li>
<li>Open Ghostly Share and wait for the app to appear automatically.</li>
<li>Select <strong>LAN</strong> for another device on the same network, or <strong>Public</strong> for an internet link.</li>
<li>Start sharing.</li>
<li>Copy the URL or open its QR code on another device.</li>
</ol>
<p>Keep Ghostly Share and the local app running while the link is in use. Stop sharing when the test or demo is finished.</p>
<h2>Use the CLI</h2>
<p>The optional <code>ghs</code> command-line tool is useful for terminals, servers, and scripts.</p>
<pre><code class="language-bash">ghs scan
ghs lan 5173
ghs share 5173
</code></pre>
<p>Both sharing commands remain active until you press <code>Ctrl+C</code>. If several discovered apps use the same port, pass the exact local URL instead of only the port.</p>
<h2>A quick safety checklist</h2>
<p>Before sharing, check that:</p>
<ul>
<li>the app does not expose secrets or customer data;</li>
<li>private admin panels and databases remain inaccessible;</li>
<li>LAN sharing is used only on a trusted network;</li>
<li>public links use a password or expiration when needed;</li>
<li>sharing is stopped as soon as the test is complete.</li>
</ul>
<h2>Get Ghostly Share</h2>
<p>Ghostly Share is availabe as a desktop app for Windows and Linux. A separate CLI is available for terminal workflows.</p>
<ul>
<li><a href="https://github.com/Nix1983/GhostlyShare-Releases/releases/latest">Download the latest release</a></li>
<li><a href="https://apps.microsoft.com/detail/9PJ6DBC342GR">Install from the Microsoft Store</a></li>
<li><a href="https://github.com/Nix1983/GhostlyShare-Releases/blob/master/wiki/Home.md">Read the user guide</a></li>
</ul>
<p>No router configuration is required. Start the local app, choose the right sharing mode, and send the link.</p>
]]></content:encoded></item></channel></rss>