<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Doraemon — RelayHook</title>
    <description>The latest articles on DEV Community by Doraemon — RelayHook (@qdoraemon).</description>
    <link>https://dev.to/qdoraemon</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4086810%2Fee3647c5-96d9-4cb2-9484-09b6ba50fb9f.jpg</url>
      <title>DEV Community: Doraemon — RelayHook</title>
      <link>https://dev.to/qdoraemon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qdoraemon"/>
    <language>en</language>
    <item>
      <title>How to Send GitHub Push Notifications to Discord Without Managing a Server</title>
      <dc:creator>Doraemon — RelayHook</dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:31:39 +0000</pubDate>
      <link>https://dev.to/qdoraemon/how-to-send-github-push-notifications-to-discord-without-managing-a-server-2mgd</link>
      <guid>https://dev.to/qdoraemon/how-to-send-github-push-notifications-to-discord-without-managing-a-server-2mgd</guid>
      <description>&lt;p&gt;GitHub webhooks are great for repository automation, but getting useful push notifications into Discord often becomes more work than expected.&lt;/p&gt;

&lt;p&gt;You need to receive the GitHub event securely, validate its signature, send it to Discord, and make sure a temporary network error does not silently lose the notification.&lt;/p&gt;

&lt;p&gt;This tutorial shows a small, secure approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub → Serverless verification bridge → RelayHook → Discord
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bridge verifies that GitHub really sent the event. RelayHook then handles delivery, retries, delivery logs, and failed-message inspection.&lt;/p&gt;

&lt;p&gt;There is no always-on server or VPS to maintain—just a small serverless route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not point GitHub directly at RelayHook?
&lt;/h2&gt;

&lt;p&gt;RelayHook production ingress endpoints require an &lt;code&gt;X-API-Key&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;GitHub's native webhook configuration supports a payload URL, content type, secret, and event selection—but it does not let you add arbitrary request headers such as &lt;code&gt;X-API-Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The solution is to use a small server-side bridge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub sends the webhook to your bridge.&lt;/li&gt;
&lt;li&gt;The bridge verifies GitHub's &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The bridge adds the RelayHook API key safely from its environment variables.&lt;/li&gt;
&lt;li&gt;RelayHook delivers the event to your Discord webhook.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your RelayHook API key never appears in GitHub settings, browser code, or repository source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;Before starting, prepare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A GitHub repository where you have admin access.&lt;/li&gt;
&lt;li&gt;A Discord Incoming Webhook URL.&lt;/li&gt;
&lt;li&gt;A RelayHook account and project.&lt;/li&gt;
&lt;li&gt;A project API key and Ingress URL from RelayHook.&lt;/li&gt;
&lt;li&gt;A serverless application. This example uses a Next.js App Router route and can be deployed on Vercel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Configure RelayHook
&lt;/h2&gt;

&lt;p&gt;Create a RelayHook project, then add your Discord Incoming Webhook URL as a Discord destination.&lt;/p&gt;

&lt;p&gt;Next, copy two values from the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RELAYHOOK_INGRESS_URL
RELAYHOOK_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the API key secret. It must only exist in server-side environment variables.&lt;/p&gt;

&lt;p&gt;For this example, keep the project in the default &lt;strong&gt;Raw&lt;/strong&gt; ingress mode. RelayHook will preserve the GitHub event envelope and deliver it as formatted JSON to Discord.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Add environment variables
&lt;/h2&gt;

&lt;p&gt;In your Next.js project, add these variables locally and in your hosting provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GITHUB_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace-with-a-long-random-secret
&lt;span class="nv"&gt;RELAYHOOK_INGRESS_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://relay-hook-api.fluxhub.dev/v1/ingress/your-project-token
&lt;span class="nv"&gt;RELAYHOOK_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rh_your_project_api_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a long random value for &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt;. You will paste the exact same value into GitHub's Webhook &lt;strong&gt;Secret&lt;/strong&gt; field later.&lt;/p&gt;

&lt;p&gt;Do not expose any of these variables with a &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create the verification bridge
&lt;/h2&gt;

&lt;p&gt;Create this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/api/github-webhook/route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hasValidSignature&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&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;GITHUB_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&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;actualBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&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;expectedBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;actualBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;expectedBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actualBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// GitHub signs the original request body.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-hub-signature-256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;hasValidSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid GitHub signature&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-github-event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// GitHub sends a ping as soon as a webhook is created.&lt;/span&gt;
  &lt;span class="k"&gt;if &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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ping&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pong&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&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;RELAYHOOK_INGRESS_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&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;RELAYHOOK_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;"&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="c1"&gt;// Useful for log correlation and downstream deduplication.&lt;/span&gt;
      &lt;span class="na"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-github-delivery&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Accepted&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RelayHook rejected the event&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route does three important things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifies the GitHub HMAC signature before trusting the request.&lt;/li&gt;
&lt;li&gt;Keeps RelayHook credentials on the server.&lt;/li&gt;
&lt;li&gt;Passes GitHub's delivery ID along with the event.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Deploy the route
&lt;/h2&gt;

&lt;p&gt;Deploy your Next.js application to Vercel or another serverless platform.&lt;/p&gt;

&lt;p&gt;After deployment, your endpoint will look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-app.vercel.app/api/github-webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use HTTPS. GitHub webhooks should never target an unsecured endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Configure the GitHub Webhook
&lt;/h2&gt;

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

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Webhooks&lt;/strong&gt; → &lt;strong&gt;Add webhook&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Payload URL&lt;/strong&gt; to your deployed bridge URL:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   https://your-app.vercel.app/api/github-webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Set &lt;strong&gt;Content type&lt;/strong&gt; to:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   application/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Paste your &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt; into &lt;strong&gt;Secret&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Let me select individual events&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Pushes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Save the webhook.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GitHub sends a &lt;code&gt;ping&lt;/code&gt; event immediately. Your bridge returns &lt;code&gt;200 pong&lt;/code&gt;, so the GitHub delivery page should show a successful response.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test it
&lt;/h2&gt;

&lt;p&gt;Push a small commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Test GitHub webhook notification"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check each layer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Open the webhook's &lt;strong&gt;Recent Deliveries&lt;/strong&gt; page and confirm a &lt;code&gt;2xx&lt;/code&gt; response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RelayHook&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Open &lt;strong&gt;Logs&lt;/strong&gt; and confirm that an ingress event and a Discord delivery job were created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Confirm that the GitHub push payload arrived in your target channel.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If Discord is temporarily unavailable, RelayHook keeps a durable delivery job, retries the request automatically, and records final failures in the dead-letter queue instead of silently losing the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub gets a 401 response
&lt;/h3&gt;

&lt;p&gt;The webhook secret and &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt; do not match, or the raw request body was modified before validation.&lt;/p&gt;

&lt;p&gt;Always calculate the HMAC using the untouched &lt;code&gt;await request.text()&lt;/code&gt; body.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub ping succeeds but push events do not arrive
&lt;/h3&gt;

&lt;p&gt;Check that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The webhook is active.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Pushes&lt;/strong&gt; event is enabled.&lt;/li&gt;
&lt;li&gt;The deployed route has the latest environment variables.&lt;/li&gt;
&lt;li&gt;Your deployment logs show incoming requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RelayHook rejects the event
&lt;/h3&gt;

&lt;p&gt;Make sure &lt;code&gt;RELAYHOOK_API_KEY&lt;/code&gt; belongs to the same RelayHook project as &lt;code&gt;RELAYHOOK_INGRESS_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discord delivery fails
&lt;/h3&gt;

&lt;p&gt;Open the failed delivery job in RelayHook Logs. A common cause is a revoked or deleted Discord Incoming Webhook URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note about duplicate delivery
&lt;/h2&gt;

&lt;p&gt;Webhook delivery is generally &lt;strong&gt;at least once&lt;/strong&gt;, not exactly once.&lt;/p&gt;

&lt;p&gt;GitHub may redeliver events, and a reliable delivery system may retry a request after a temporary failure. If your downstream action must happen exactly once, use GitHub's &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; value (sent here as &lt;code&gt;event_id&lt;/code&gt;) to deduplicate it in your own application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;A small serverless bridge is enough to securely connect GitHub to RelayHook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub verifies → RelayHook persists → Discord receives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RelayHook gives you a central place to fan out events to Discord, Slack, Telegram, or email—with delivery logs, retries, and a dead-letter queue.&lt;/p&gt;

&lt;p&gt;Try RelayHook here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://relay-hook-app.fluxhub.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=github_discord" rel="noopener noreferrer"&gt;https://relay-hook-app.fluxhub.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=github_discord&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>discord</category>
      <category>webhooks</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
