[{"data":1,"prerenderedAt":765},["ShallowReactive",2],{"content-query-cMw11JEMcC":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"layout":10,"crumb":11,"term":11,"summary":12,"group":13,"order":14,"updated":15,"cta":16,"related":23,"faq":42,"body":58,"_type":758,"_id":759,"_source":760,"_file":761,"_stem":762,"_extension":763,"sitemap":764},"/glossary/webhook-forwarding","glossary",false,"","What is webhook forwarding? How to forward webhooks","Webhook forwarding receives webhooks on a public URL and relays them to another destination: localhost, a private server or several endpoints. How it works and how to set it up.","reference","Webhook forwarding","Receive webhooks on a public URL and relay them somewhere else.","Infrastructure",30,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":11,"secondaryTo":21,"secondaryLabel":22},"Webhook Relay","Forward webhooks to localhost, private servers or many endpoints","Get a permanent public URL, then deliver each webhook to localhost, an on-prem server with no public IP, or up to 200 destinations, with durable retries, logs, transforms and throttling. Free plan available.","/webhook-gateway/","/docs/webhooks/internal/localhost/","Forward to localhost",[24,27,30,33,36,39],{"title":25,"to":26},"Webhook gateway","/glossary/webhook-gateway/",{"title":28,"to":29},"Tunnel","/glossary/tunnel/",{"title":31,"to":32},"Webhook fan-out","/glossary/webhook-fan-out/",{"title":34,"to":35},"Webhook listener","/glossary/webhook-listener/",{"title":37,"to":38},"localhost:3000","/localhost/3000/",{"title":40,"to":41},"Receive webhooks on localhost","/blog/receive-webhooks-on-localhost/",[43,46,49,52,55],{"q":44,"a":45},"What is webhook forwarding?","Webhook forwarding is receiving webhooks on one public URL and relaying each request to another destination, such as a server on localhost, a machine on a private network, or several endpoints at once. The provider only ever sees the public URL; the forwarder delivers the request onward with its body and headers intact.",{"q":47,"a":48},"How do I forward webhooks to localhost?","Use a forwarding tool that gives you a public URL and runs a small agent on your machine. With Webhook Relay, install the relay CLI, log in, and run relay forward --bucket my-app http://localhost:3000/webhooks. Give the printed URL to the provider, and each webhook is delivered to your local server over the agent's outbound connection.",{"q":50,"a":51},"What is the difference between webhook forwarding and a tunnel?","A tunnel exposes a whole local port to the internet, so anything can reach your server through the public hostname, and the request fails if the tunnel is down. Webhook forwarding only relays webhook requests sent to a specific URL, can store and retry them while your machine is offline, and can deliver one request to many destinations.",{"q":53,"a":54},"Does webhook forwarding break signature verification?","It shouldn't. A forwarder that passes the raw body and headers through unchanged lets your endpoint verify the provider's signature as usual. Only deliberate transforms that change the body invalidate the original signature.",{"q":56,"a":57},"Do I need to open a firewall port to forward webhooks?","Not with an agent-based forwarder. The agent on your machine opens an outbound connection to the forwarding service, and webhooks are delivered over that connection, so no inbound port, public IP or router change is needed.",{"type":59,"children":60,"toc":751},"root",[61,93,156,163,251,257,265,292,300,338,346,352,474,479,485,505,535,553,558,653,673,679,745],{"type":62,"tag":63,"props":64,"children":65},"element","p",{},[66,82,84,91],{"type":62,"tag":67,"props":68,"children":69},"strong",{},[70,73,80],{"type":71,"value":72},"text","Webhook forwarding is receiving webhooks on a public URL and relaying each request on to another destination: a server on ",{"type":62,"tag":74,"props":75,"children":77},"code",{"className":76},[],[78],{"type":71,"value":79},"localhost",{"type":71,"value":81},", a machine on a private network with no public IP, or several endpoints at once.",{"type":71,"value":83}," The provider only sees the public URL. The forwarder delivers each request onward with its body and headers intact, so your ",{"type":62,"tag":85,"props":86,"children":88},"a",{"href":87},"/glossary/webhook-endpoint/",[89],{"type":71,"value":90},"endpoint",{"type":71,"value":92}," handles it as if the provider had called it directly.",{"type":62,"tag":94,"props":95,"children":99},"pre",{"code":96,"language":97,"meta":7,"className":98,"style":7},"relay forward --bucket my-app http://localhost:3000/webhooks\n# prints a permanent public input URL, e.g. https://my.webhookrelay.com/v1/webhooks/2a1b…\n# give that URL to the provider; each webhook is delivered to localhost:3000/webhooks\n","bash","language-bash shiki shiki-themes github-dark",[100],{"type":62,"tag":74,"props":101,"children":102},{"__ignoreMap":7},[103,137,147],{"type":62,"tag":104,"props":105,"children":108},"span",{"class":106,"line":107},"line",1,[109,115,121,127,132],{"type":62,"tag":104,"props":110,"children":112},{"style":111},"--shiki-default:#B392F0",[113],{"type":71,"value":114},"relay",{"type":62,"tag":104,"props":116,"children":118},{"style":117},"--shiki-default:#9ECBFF",[119],{"type":71,"value":120}," forward",{"type":62,"tag":104,"props":122,"children":124},{"style":123},"--shiki-default:#79B8FF",[125],{"type":71,"value":126}," --bucket",{"type":62,"tag":104,"props":128,"children":129},{"style":117},[130],{"type":71,"value":131}," my-app",{"type":62,"tag":104,"props":133,"children":134},{"style":117},[135],{"type":71,"value":136}," http://localhost:3000/webhooks\n",{"type":62,"tag":104,"props":138,"children":140},{"class":106,"line":139},2,[141],{"type":62,"tag":104,"props":142,"children":144},{"style":143},"--shiki-default:#6A737D",[145],{"type":71,"value":146},"# prints a permanent public input URL, e.g. https://my.webhookrelay.com/v1/webhooks/2a1b…\n",{"type":62,"tag":104,"props":148,"children":150},{"class":106,"line":149},3,[151],{"type":62,"tag":104,"props":152,"children":153},{"style":143},[154],{"type":71,"value":155},"# give that URL to the provider; each webhook is delivered to localhost:3000/webhooks\n",{"type":62,"tag":157,"props":158,"children":160},"h2",{"id":159},"why-forward-webhooks",[161],{"type":71,"value":162},"Why forward webhooks",{"type":62,"tag":164,"props":165,"children":166},"ul",{},[167,188,198,223,241],{"type":62,"tag":168,"props":169,"children":170},"li",{},[171,176,178,186],{"type":62,"tag":67,"props":172,"children":173},{},[174],{"type":71,"value":175},"Local development.",{"type":71,"value":177}," Providers can't reach ",{"type":62,"tag":85,"props":179,"children":180},{"href":38},[181],{"type":62,"tag":74,"props":182,"children":184},{"className":183},[],[185],{"type":71,"value":37},{"type":71,"value":187},". Forwarding lets you test real Stripe, GitHub or Shopify webhooks against the code on your laptop.",{"type":62,"tag":168,"props":189,"children":190},{},[191,196],{"type":62,"tag":67,"props":192,"children":193},{},[194],{"type":71,"value":195},"Private and on-prem servers.",{"type":71,"value":197}," Jenkins, Home Assistant, internal APIs and Kubernetes services often have no public IP, and opening a firewall port isn't an option.",{"type":62,"tag":168,"props":199,"children":200},{},[201,206,208,214,216,221],{"type":62,"tag":67,"props":202,"children":203},{},[204],{"type":71,"value":205},"One URL, many consumers.",{"type":71,"value":207}," A provider allows one ",{"type":62,"tag":85,"props":209,"children":211},{"href":210},"/glossary/webhook-url/",[212],{"type":71,"value":213},"webhook URL",{"type":71,"value":215},", but production, staging and analytics all need the events. See ",{"type":62,"tag":85,"props":217,"children":218},{"href":32},[219],{"type":71,"value":220},"webhook fan-out",{"type":71,"value":222},".",{"type":62,"tag":168,"props":224,"children":225},{},[226,231,233,239],{"type":62,"tag":67,"props":227,"children":228},{},[229],{"type":71,"value":230},"Reliability.",{"type":71,"value":232}," The forwarder can store each webhook and ",{"type":62,"tag":85,"props":234,"children":236},{"href":235},"/glossary/webhook-retries/",[237],{"type":71,"value":238},"retry",{"type":71,"value":240}," delivery long after the provider would have given up.",{"type":62,"tag":168,"props":242,"children":243},{},[244,249],{"type":62,"tag":67,"props":245,"children":246},{},[247],{"type":71,"value":248},"A stable URL.",{"type":71,"value":250}," Move the destination between servers, clouds or environments without touching the provider's settings.",{"type":62,"tag":157,"props":252,"children":254},{"id":253},"how-webhook-forwarding-works",[255],{"type":71,"value":256},"How webhook forwarding works",{"type":62,"tag":63,"props":258,"children":259},{},[260],{"type":62,"tag":67,"props":261,"children":262},{},[263],{"type":71,"value":264},"To a public destination (server-side):",{"type":62,"tag":266,"props":267,"children":268},"ol",{},[269,282,287],{"type":62,"tag":168,"props":270,"children":271},{},[272,274,280],{"type":71,"value":273},"The provider sends a ",{"type":62,"tag":74,"props":275,"children":277},{"className":276},[],[278],{"type":71,"value":279},"POST",{"type":71,"value":281}," to the forwarder's public URL.",{"type":62,"tag":168,"props":283,"children":284},{},[285],{"type":71,"value":286},"The forwarder accepts and stores it, and responds to the provider.",{"type":62,"tag":168,"props":288,"children":289},{},[290],{"type":71,"value":291},"It sends the same request to each configured destination URL, retrying on failure.",{"type":62,"tag":63,"props":293,"children":294},{},[295],{"type":62,"tag":67,"props":296,"children":297},{},[298],{"type":71,"value":299},"To localhost or a private network (agent-based):",{"type":62,"tag":266,"props":301,"children":302},{},[303,315,320,325],{"type":62,"tag":168,"props":304,"children":305},{},[306,308,313],{"type":71,"value":307},"A small agent runs inside your network and opens an ",{"type":62,"tag":67,"props":309,"children":310},{},[311],{"type":71,"value":312},"outbound",{"type":71,"value":314}," connection to the forwarding service. No inbound port is opened.",{"type":62,"tag":168,"props":316,"children":317},{},[318],{"type":71,"value":319},"The provider sends a webhook to the public URL.",{"type":62,"tag":168,"props":321,"children":322},{},[323],{"type":71,"value":324},"The service pushes the request down the agent's connection.",{"type":62,"tag":168,"props":326,"children":327},{},[328,330,336],{"type":71,"value":329},"The agent makes the HTTP request to your local destination, for example ",{"type":62,"tag":74,"props":331,"children":333},{"className":332},[],[334],{"type":71,"value":335},"http://localhost:3000/webhooks",{"type":71,"value":337},", and reports the response.",{"type":62,"tag":94,"props":339,"children":341},{"code":340},"Provider ──HTTPS──▶ public URL ──▶ forwarding service\n                                        │  (outbound connection opened by the agent)\n                                        ▼\n                     your network:  relay agent ──▶ http://localhost:3000/webhooks\n",[342],{"type":62,"tag":74,"props":343,"children":344},{"__ignoreMap":7},[345],{"type":71,"value":340},{"type":62,"tag":157,"props":347,"children":349},{"id":348},"webhook-forwarding-vs-a-tunnel",[350],{"type":71,"value":351},"Webhook forwarding vs a tunnel",{"type":62,"tag":353,"props":354,"children":355},"table",{},[356,379],{"type":62,"tag":357,"props":358,"children":359},"thead",{},[360],{"type":62,"tag":361,"props":362,"children":363},"tr",{},[364,368,372],{"type":62,"tag":365,"props":366,"children":367},"th",{},[],{"type":62,"tag":365,"props":369,"children":370},{},[371],{"type":71,"value":11},{"type":62,"tag":365,"props":373,"children":374},{},[375],{"type":62,"tag":85,"props":376,"children":377},{"href":29},[378],{"type":71,"value":28},{"type":62,"tag":380,"props":381,"children":382},"tbody",{},[383,402,420,438,456],{"type":62,"tag":361,"props":384,"children":385},{},[386,392,397],{"type":62,"tag":387,"props":388,"children":389},"td",{},[390],{"type":71,"value":391},"What's exposed",{"type":62,"tag":387,"props":393,"children":394},{},[395],{"type":71,"value":396},"Only requests sent to the webhook URL",{"type":62,"tag":387,"props":398,"children":399},{},[400],{"type":71,"value":401},"The whole local port",{"type":62,"tag":361,"props":403,"children":404},{},[405,410,415],{"type":62,"tag":387,"props":406,"children":407},{},[408],{"type":71,"value":409},"If your machine is offline",{"type":62,"tag":387,"props":411,"children":412},{},[413],{"type":71,"value":414},"Requests are stored and can be retried or replayed",{"type":62,"tag":387,"props":416,"children":417},{},[418],{"type":71,"value":419},"Requests fail",{"type":62,"tag":361,"props":421,"children":422},{},[423,428,433],{"type":62,"tag":387,"props":424,"children":425},{},[426],{"type":71,"value":427},"Many destinations from one URL",{"type":62,"tag":387,"props":429,"children":430},{},[431],{"type":71,"value":432},"Yes",{"type":62,"tag":387,"props":434,"children":435},{},[436],{"type":71,"value":437},"No",{"type":62,"tag":361,"props":439,"children":440},{},[441,446,451],{"type":62,"tag":387,"props":442,"children":443},{},[444],{"type":71,"value":445},"Logs, retries, transforms",{"type":62,"tag":387,"props":447,"children":448},{},[449],{"type":71,"value":450},"Built in",{"type":62,"tag":387,"props":452,"children":453},{},[454],{"type":71,"value":455},"Usually basic inspection",{"type":62,"tag":361,"props":457,"children":458},{},[459,464,469],{"type":62,"tag":387,"props":460,"children":461},{},[462],{"type":71,"value":463},"Best for",{"type":62,"tag":387,"props":465,"children":466},{},[467],{"type":71,"value":468},"Webhooks",{"type":62,"tag":387,"props":470,"children":471},{},[472],{"type":71,"value":473},"Sharing a web app, APIs, browser access",{"type":62,"tag":63,"props":475,"children":476},{},[477],{"type":71,"value":478},"Use a tunnel when people or apps need interactive access to your server. Use forwarding when you're receiving webhooks.",{"type":62,"tag":157,"props":480,"children":482},{"id":481},"setting-it-up-with-webhook-relay",[483],{"type":71,"value":484},"Setting it up with Webhook Relay",{"type":62,"tag":63,"props":486,"children":487},{},[488,490,495,497,503],{"type":71,"value":489},"Install the ",{"type":62,"tag":74,"props":491,"children":493},{"className":492},[],[494],{"type":71,"value":114},{"type":71,"value":496}," CLI, run ",{"type":62,"tag":74,"props":498,"children":500},{"className":499},[],[501],{"type":71,"value":502},"relay login",{"type":71,"value":504},", then:",{"type":62,"tag":94,"props":506,"children":508},{"code":507,"language":97,"meta":7,"className":98,"style":7},"relay forward --bucket my-app http://localhost:3000/webhooks\n",[509],{"type":62,"tag":74,"props":510,"children":511},{"__ignoreMap":7},[512],{"type":62,"tag":104,"props":513,"children":514},{"class":106,"line":107},[515,519,523,527,531],{"type":62,"tag":104,"props":516,"children":517},{"style":111},[518],{"type":71,"value":114},{"type":62,"tag":104,"props":520,"children":521},{"style":117},[522],{"type":71,"value":120},{"type":62,"tag":104,"props":524,"children":525},{"style":123},[526],{"type":71,"value":126},{"type":62,"tag":104,"props":528,"children":529},{"style":117},[530],{"type":71,"value":131},{"type":62,"tag":104,"props":532,"children":533},{"style":117},[534],{"type":71,"value":136},{"type":62,"tag":63,"props":536,"children":537},{},[538,540,545,547,552],{"type":71,"value":539},"The command creates a bucket with a ",{"type":62,"tag":67,"props":541,"children":542},{},[543],{"type":71,"value":544},"permanent",{"type":71,"value":546}," public input URL and starts the agent. Re-running it with the same bucket name keeps the same URL, so you configure the provider once. Several developers can forward the same bucket and every connected agent receives every webhook. The full walkthrough, including Docker and troubleshooting, is in ",{"type":62,"tag":85,"props":548,"children":549},{"href":21},[550],{"type":71,"value":551},"receiving webhooks on localhost",{"type":71,"value":222},{"type":62,"tag":63,"props":554,"children":555},{},[556],{"type":71,"value":557},"On top of plain forwarding, each destination can have:",{"type":62,"tag":164,"props":559,"children":560},{},[561,575,589,603,625,639],{"type":62,"tag":168,"props":562,"children":563},{},[564,573],{"type":62,"tag":67,"props":565,"children":566},{},[567],{"type":62,"tag":85,"props":568,"children":570},{"href":569},"/features/durable-retries/",[571],{"type":71,"value":572},"Durable retries",{"type":71,"value":574}," with exponential backoff for up to 30 days.",{"type":62,"tag":168,"props":576,"children":577},{},[578,587],{"type":62,"tag":67,"props":579,"children":580},{},[581],{"type":62,"tag":85,"props":582,"children":584},{"href":583},"/docs/webhooks/replay-on-agent-connect/",[585],{"type":71,"value":586},"Replay on agent connect",{"type":71,"value":588},", which delivers webhooks that arrived while your agent was offline as soon as it reconnects.",{"type":62,"tag":168,"props":590,"children":591},{},[592,601],{"type":62,"tag":67,"props":593,"children":594},{},[595],{"type":62,"tag":85,"props":596,"children":598},{"href":597},"/features/webhook-multiple-destinations/",[599],{"type":71,"value":600},"Multiple destinations",{"type":71,"value":602},", up to 200 per bucket, mixing public URLs, internal servers and Slack or Discord.",{"type":62,"tag":168,"props":604,"children":605},{},[606,615,617,623],{"type":62,"tag":67,"props":607,"children":608},{},[609],{"type":62,"tag":85,"props":610,"children":612},{"href":611},"/features/transform-webhooks/",[613],{"type":71,"value":614},"Transforms",{"type":71,"value":616}," and ",{"type":62,"tag":85,"props":618,"children":620},{"href":619},"/features/forwarding-rules/",[621],{"type":71,"value":622},"forwarding rules",{"type":71,"value":624}," to reshape or filter events.",{"type":62,"tag":168,"props":626,"children":627},{},[628,637],{"type":62,"tag":67,"props":629,"children":630},{},[631],{"type":62,"tag":85,"props":632,"children":634},{"href":633},"/features/throttling/",[635],{"type":71,"value":636},"Throttling",{"type":71,"value":638}," so a burst doesn't overwhelm a small server.",{"type":62,"tag":168,"props":640,"children":641},{},[642,651],{"type":62,"tag":67,"props":643,"children":644},{},[645],{"type":62,"tag":85,"props":646,"children":648},{"href":647},"/features/webhook-logs/",[649],{"type":71,"value":650},"Logs",{"type":71,"value":652}," of every request and response, with one-click resend.",{"type":62,"tag":63,"props":654,"children":655},{},[656,658,663,665,671],{"type":71,"value":657},"The ",{"type":62,"tag":85,"props":659,"children":660},{"href":20},[661],{"type":71,"value":662},"webhook forwarding overview",{"type":71,"value":664}," covers all of these, and ",{"type":62,"tag":85,"props":666,"children":668},{"href":667},"/features/webhook-to-internal-server/",[669],{"type":71,"value":670},"forwarding to internal servers",{"type":71,"value":672}," covers production use behind a firewall.",{"type":62,"tag":157,"props":674,"children":676},{"id":675},"common-mistakes",[677],{"type":71,"value":678},"Common mistakes",{"type":62,"tag":164,"props":680,"children":681},{},[682,699,717,727],{"type":62,"tag":168,"props":683,"children":684},{},[685,690,692,697],{"type":62,"tag":67,"props":686,"children":687},{},[688],{"type":71,"value":689},"Forwarding to the wrong path.",{"type":71,"value":691}," The destination URL needs the full route, ",{"type":62,"tag":74,"props":693,"children":695},{"className":694},[],[696],{"type":71,"value":335},{"type":71,"value":698},", not just the port.",{"type":62,"tag":168,"props":700,"children":701},{},[702,707,709,715],{"type":62,"tag":67,"props":703,"children":704},{},[705],{"type":71,"value":706},"A forwarder that modifies the body.",{"type":71,"value":708}," Re-encoded JSON breaks ",{"type":62,"tag":85,"props":710,"children":712},{"href":711},"/glossary/webhook-signature/",[713],{"type":71,"value":714},"signature verification",{"type":71,"value":716},". Forward raw bytes unless you mean to transform.",{"type":62,"tag":168,"props":718,"children":719},{},[720,725],{"type":62,"tag":67,"props":721,"children":722},{},[723],{"type":71,"value":724},"Using a random URL that changes.",{"type":71,"value":726}," Every restart then means updating the provider's settings, and events sent to the old URL are lost.",{"type":62,"tag":168,"props":728,"children":729},{},[730,735,737,743],{"type":62,"tag":67,"props":731,"children":732},{},[733],{"type":71,"value":734},"Looking in the wrong place for errors.",{"type":71,"value":736}," Unless the forwarder is set to pass your endpoint's response back, the provider sees the forwarder's response, not your endpoint's. Check the forwarder's logs to see whether your code failed. Webhook Relay can return a ",{"type":62,"tag":85,"props":738,"children":740},{"href":739},"/docs/webhooks/custom-webhook-response/",[741],{"type":71,"value":742},"dynamic or static response",{"type":71,"value":744}," when a provider needs one.",{"type":62,"tag":746,"props":747,"children":748},"style",{},[749],{"type":71,"value":750},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":149,"depth":149,"links":752},[753,754,755,756,757],{"id":159,"depth":139,"text":162},{"id":253,"depth":139,"text":256},{"id":348,"depth":139,"text":351},{"id":481,"depth":139,"text":484},{"id":675,"depth":139,"text":678},"markdown","content:glossary:webhook-forwarding.md","content","glossary/webhook-forwarding.md","glossary/webhook-forwarding","md",{"loc":4},1793199807458]