[{"data":1,"prerenderedAt":1117},["ShallowReactive",2],{"content-query-EE2N6TpWF8":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":24,"faq":42,"body":55,"_type":1110,"_id":1111,"_source":1112,"_file":1113,"_stem":1114,"_extension":1115,"sitemap":1116},"/glossary/webhook-listener","glossary",false,"","What is a webhook listener? Definition and example","A webhook listener is the server or process that waits for incoming webhook requests and handles them. How it works, example listeners in Node.js and Python, and how to run one locally.","reference","Webhook listener","The server or process that waits for and handles incoming webhooks.","Basics",5,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay","Run your webhook listener on localhost","relay forward gives your local listener a permanent public URL. Providers send to that URL, and each webhook reaches your machine over an outbound connection, with no open ports. Every request is logged and can be resent.","/docs/webhooks/internal/localhost/","Receive webhooks locally","/webhook-gateway/","Webhook forwarding",[25,28,31,33,36,39],{"title":26,"to":27},"Webhook endpoint","/glossary/webhook-endpoint/",{"title":29,"to":30},"Webhook URL","/glossary/webhook-url/",{"title":23,"to":32},"/glossary/webhook-forwarding/",{"title":34,"to":35},"Tunnel","/glossary/tunnel/",{"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],{"q":44,"a":45},"What is a webhook listener?","A webhook listener is the server or process that waits for incoming webhook HTTP requests and handles them. It listens on a port, accepts POST requests on a route such as /webhooks, verifies the signature, hands the event to your code and replies with a 2xx status.",{"q":47,"a":48},"What is the difference between a webhook listener and a webhook endpoint?","The endpoint is the URL and route that receives webhooks. The listener is the running program behind it, the HTTP server bound to a port. One listener can serve many endpoints, for example one route per provider.",{"q":50,"a":51},"How do I run a webhook listener locally?","Start your listener on a local port, for example a small Express or Flask app on port 3000, then use a forwarding tool to give it a public URL. With Webhook Relay, relay forward --bucket my-app http://localhost:3000/webhooks prints a permanent public URL that delivers every webhook to the local listener.",{"q":53,"a":54},"Does a webhook listener need to be always on?","Ideally, yes, because providers send webhooks whenever events happen. If the listener is down, deliveries fail and depend on the provider's retries. A webhook gateway with durable retries can store the events and deliver them once the listener is back.",{"type":56,"children":57,"toc":1103},"root",[58,112,119,124,508,513,625,631,753,759,856,869,875,890,936,973,984,990,1097],{"type":59,"tag":60,"props":61,"children":62},"element","p",{},[63,70,72,79,81,87,89,95,97,103,105,110],{"type":59,"tag":64,"props":65,"children":66},"strong",{},[67],{"type":68,"value":69},"text","A webhook listener is the server or process that waits for incoming webhook requests and handles them.",{"type":68,"value":71}," It binds to a port, accepts ",{"type":59,"tag":73,"props":74,"children":76},"code",{"className":75},[],[77],{"type":68,"value":78},"POST",{"type":68,"value":80}," requests on a route such as ",{"type":59,"tag":73,"props":82,"children":84},{"className":83},[],[85],{"type":68,"value":86},"/webhooks",{"type":68,"value":88},", verifies each request, passes the event to your code and replies with a ",{"type":59,"tag":73,"props":90,"children":92},{"className":91},[],[93],{"type":68,"value":94},"2xx",{"type":68,"value":96}," status. The listener is the running program; the ",{"type":59,"tag":98,"props":99,"children":100},"a",{"href":27},[101],{"type":68,"value":102},"webhook endpoint",{"type":68,"value":104}," is the route it serves; the ",{"type":59,"tag":98,"props":106,"children":107},{"href":30},[108],{"type":68,"value":109},"webhook URL",{"type":68,"value":111}," is the public address providers send to.",{"type":59,"tag":113,"props":114,"children":116},"h2",{"id":115},"a-minimal-webhook-listener",[117],{"type":68,"value":118},"A minimal webhook listener",{"type":59,"tag":60,"props":120,"children":121},{},[122],{"type":68,"value":123},"Node.js with Express:",{"type":59,"tag":125,"props":126,"children":130},"pre",{"className":127,"code":128,"language":129,"meta":7,"style":7},"language-js shiki shiki-themes github-dark","import express from 'express'\nconst app = express()\n\napp.post('/webhooks', express.raw({ type: '*/*' }), (req, res) => {\n  if (!verifySignature(req.body, req.headers)) return res.sendStatus(401)\n  const event = JSON.parse(req.body)\n  queue.add(event)       // process later\n  res.sendStatus(204)    // acknowledge fast\n})\n\napp.listen(3000, () => console.log('listening on :3000'))\n","js",[131],{"type":59,"tag":73,"props":132,"children":133},{"__ignoreMap":7},[134,163,194,204,284,341,379,404,436,445,453],{"type":59,"tag":135,"props":136,"children":139},"span",{"class":137,"line":138},"line",1,[140,146,152,157],{"type":59,"tag":135,"props":141,"children":143},{"style":142},"--shiki-default:#F97583",[144],{"type":68,"value":145},"import",{"type":59,"tag":135,"props":147,"children":149},{"style":148},"--shiki-default:#E1E4E8",[150],{"type":68,"value":151}," express ",{"type":59,"tag":135,"props":153,"children":154},{"style":142},[155],{"type":68,"value":156},"from",{"type":59,"tag":135,"props":158,"children":160},{"style":159},"--shiki-default:#9ECBFF",[161],{"type":68,"value":162}," 'express'\n",{"type":59,"tag":135,"props":164,"children":166},{"class":137,"line":165},2,[167,172,178,183,189],{"type":59,"tag":135,"props":168,"children":169},{"style":142},[170],{"type":68,"value":171},"const",{"type":59,"tag":135,"props":173,"children":175},{"style":174},"--shiki-default:#79B8FF",[176],{"type":68,"value":177}," app",{"type":59,"tag":135,"props":179,"children":180},{"style":142},[181],{"type":68,"value":182}," =",{"type":59,"tag":135,"props":184,"children":186},{"style":185},"--shiki-default:#B392F0",[187],{"type":68,"value":188}," express",{"type":59,"tag":135,"props":190,"children":191},{"style":148},[192],{"type":68,"value":193},"()\n",{"type":59,"tag":135,"props":195,"children":197},{"class":137,"line":196},3,[198],{"type":59,"tag":135,"props":199,"children":201},{"emptyLinePlaceholder":200},true,[202],{"type":68,"value":203},"\n",{"type":59,"tag":135,"props":205,"children":207},{"class":137,"line":206},4,[208,213,218,223,228,233,238,243,248,253,259,264,269,274,279],{"type":59,"tag":135,"props":209,"children":210},{"style":148},[211],{"type":68,"value":212},"app.",{"type":59,"tag":135,"props":214,"children":215},{"style":185},[216],{"type":68,"value":217},"post",{"type":59,"tag":135,"props":219,"children":220},{"style":148},[221],{"type":68,"value":222},"(",{"type":59,"tag":135,"props":224,"children":225},{"style":159},[226],{"type":68,"value":227},"'/webhooks'",{"type":59,"tag":135,"props":229,"children":230},{"style":148},[231],{"type":68,"value":232},", express.",{"type":59,"tag":135,"props":234,"children":235},{"style":185},[236],{"type":68,"value":237},"raw",{"type":59,"tag":135,"props":239,"children":240},{"style":148},[241],{"type":68,"value":242},"({ type: ",{"type":59,"tag":135,"props":244,"children":245},{"style":159},[246],{"type":68,"value":247},"'*/*'",{"type":59,"tag":135,"props":249,"children":250},{"style":148},[251],{"type":68,"value":252}," }), (",{"type":59,"tag":135,"props":254,"children":256},{"style":255},"--shiki-default:#FFAB70",[257],{"type":68,"value":258},"req",{"type":59,"tag":135,"props":260,"children":261},{"style":148},[262],{"type":68,"value":263},", ",{"type":59,"tag":135,"props":265,"children":266},{"style":255},[267],{"type":68,"value":268},"res",{"type":59,"tag":135,"props":270,"children":271},{"style":148},[272],{"type":68,"value":273},") ",{"type":59,"tag":135,"props":275,"children":276},{"style":142},[277],{"type":68,"value":278},"=>",{"type":59,"tag":135,"props":280,"children":281},{"style":148},[282],{"type":68,"value":283}," {\n",{"type":59,"tag":135,"props":285,"children":286},{"class":137,"line":14},[287,292,297,302,307,312,317,322,327,331,336],{"type":59,"tag":135,"props":288,"children":289},{"style":142},[290],{"type":68,"value":291},"  if",{"type":59,"tag":135,"props":293,"children":294},{"style":148},[295],{"type":68,"value":296}," (",{"type":59,"tag":135,"props":298,"children":299},{"style":142},[300],{"type":68,"value":301},"!",{"type":59,"tag":135,"props":303,"children":304},{"style":185},[305],{"type":68,"value":306},"verifySignature",{"type":59,"tag":135,"props":308,"children":309},{"style":148},[310],{"type":68,"value":311},"(req.body, req.headers)) ",{"type":59,"tag":135,"props":313,"children":314},{"style":142},[315],{"type":68,"value":316},"return",{"type":59,"tag":135,"props":318,"children":319},{"style":148},[320],{"type":68,"value":321}," res.",{"type":59,"tag":135,"props":323,"children":324},{"style":185},[325],{"type":68,"value":326},"sendStatus",{"type":59,"tag":135,"props":328,"children":329},{"style":148},[330],{"type":68,"value":222},{"type":59,"tag":135,"props":332,"children":333},{"style":174},[334],{"type":68,"value":335},"401",{"type":59,"tag":135,"props":337,"children":338},{"style":148},[339],{"type":68,"value":340},")\n",{"type":59,"tag":135,"props":342,"children":344},{"class":137,"line":343},6,[345,350,355,359,364,369,374],{"type":59,"tag":135,"props":346,"children":347},{"style":142},[348],{"type":68,"value":349},"  const",{"type":59,"tag":135,"props":351,"children":352},{"style":174},[353],{"type":68,"value":354}," event",{"type":59,"tag":135,"props":356,"children":357},{"style":142},[358],{"type":68,"value":182},{"type":59,"tag":135,"props":360,"children":361},{"style":174},[362],{"type":68,"value":363}," JSON",{"type":59,"tag":135,"props":365,"children":366},{"style":148},[367],{"type":68,"value":368},".",{"type":59,"tag":135,"props":370,"children":371},{"style":185},[372],{"type":68,"value":373},"parse",{"type":59,"tag":135,"props":375,"children":376},{"style":148},[377],{"type":68,"value":378},"(req.body)\n",{"type":59,"tag":135,"props":380,"children":382},{"class":137,"line":381},7,[383,388,393,398],{"type":59,"tag":135,"props":384,"children":385},{"style":148},[386],{"type":68,"value":387},"  queue.",{"type":59,"tag":135,"props":389,"children":390},{"style":185},[391],{"type":68,"value":392},"add",{"type":59,"tag":135,"props":394,"children":395},{"style":148},[396],{"type":68,"value":397},"(event)       ",{"type":59,"tag":135,"props":399,"children":401},{"style":400},"--shiki-default:#6A737D",[402],{"type":68,"value":403},"// process later\n",{"type":59,"tag":135,"props":405,"children":407},{"class":137,"line":406},8,[408,413,417,421,426,431],{"type":59,"tag":135,"props":409,"children":410},{"style":148},[411],{"type":68,"value":412},"  res.",{"type":59,"tag":135,"props":414,"children":415},{"style":185},[416],{"type":68,"value":326},{"type":59,"tag":135,"props":418,"children":419},{"style":148},[420],{"type":68,"value":222},{"type":59,"tag":135,"props":422,"children":423},{"style":174},[424],{"type":68,"value":425},"204",{"type":59,"tag":135,"props":427,"children":428},{"style":148},[429],{"type":68,"value":430},")    ",{"type":59,"tag":135,"props":432,"children":433},{"style":400},[434],{"type":68,"value":435},"// acknowledge fast\n",{"type":59,"tag":135,"props":437,"children":439},{"class":137,"line":438},9,[440],{"type":59,"tag":135,"props":441,"children":442},{"style":148},[443],{"type":68,"value":444},"})\n",{"type":59,"tag":135,"props":446,"children":448},{"class":137,"line":447},10,[449],{"type":59,"tag":135,"props":450,"children":451},{"emptyLinePlaceholder":200},[452],{"type":68,"value":203},{"type":59,"tag":135,"props":454,"children":456},{"class":137,"line":455},11,[457,461,466,470,475,480,484,489,494,498,503],{"type":59,"tag":135,"props":458,"children":459},{"style":148},[460],{"type":68,"value":212},{"type":59,"tag":135,"props":462,"children":463},{"style":185},[464],{"type":68,"value":465},"listen",{"type":59,"tag":135,"props":467,"children":468},{"style":148},[469],{"type":68,"value":222},{"type":59,"tag":135,"props":471,"children":472},{"style":174},[473],{"type":68,"value":474},"3000",{"type":59,"tag":135,"props":476,"children":477},{"style":148},[478],{"type":68,"value":479},", () ",{"type":59,"tag":135,"props":481,"children":482},{"style":142},[483],{"type":68,"value":278},{"type":59,"tag":135,"props":485,"children":486},{"style":148},[487],{"type":68,"value":488}," console.",{"type":59,"tag":135,"props":490,"children":491},{"style":185},[492],{"type":68,"value":493},"log",{"type":59,"tag":135,"props":495,"children":496},{"style":148},[497],{"type":68,"value":222},{"type":59,"tag":135,"props":499,"children":500},{"style":159},[501],{"type":68,"value":502},"'listening on :3000'",{"type":59,"tag":135,"props":504,"children":505},{"style":148},[506],{"type":68,"value":507},"))\n",{"type":59,"tag":60,"props":509,"children":510},{},[511],{"type":68,"value":512},"Python with Flask:",{"type":59,"tag":125,"props":514,"children":518},{"className":515,"code":516,"language":517,"meta":7,"style":7},"language-python shiki shiki-themes github-dark","from flask import Flask, request\n\napp = Flask(__name__)\n\n@app.post(\"/webhooks\")\ndef webhooks():\n    raw = request.get_data()          # raw bytes for signature checks\n    if not verify_signature(raw, request.headers):\n        return \"\", 401\n    enqueue(raw)\n    return \"\", 204\n\napp.run(port=3000)\n","python",[519],{"type":59,"tag":73,"props":520,"children":521},{"__ignoreMap":7},[522,530,537,545,552,560,568,576,584,592,600,608,616],{"type":59,"tag":135,"props":523,"children":524},{"class":137,"line":138},[525],{"type":59,"tag":135,"props":526,"children":527},{},[528],{"type":68,"value":529},"from flask import Flask, request\n",{"type":59,"tag":135,"props":531,"children":532},{"class":137,"line":165},[533],{"type":59,"tag":135,"props":534,"children":535},{"emptyLinePlaceholder":200},[536],{"type":68,"value":203},{"type":59,"tag":135,"props":538,"children":539},{"class":137,"line":196},[540],{"type":59,"tag":135,"props":541,"children":542},{},[543],{"type":68,"value":544},"app = Flask(__name__)\n",{"type":59,"tag":135,"props":546,"children":547},{"class":137,"line":206},[548],{"type":59,"tag":135,"props":549,"children":550},{"emptyLinePlaceholder":200},[551],{"type":68,"value":203},{"type":59,"tag":135,"props":553,"children":554},{"class":137,"line":14},[555],{"type":59,"tag":135,"props":556,"children":557},{},[558],{"type":68,"value":559},"@app.post(\"/webhooks\")\n",{"type":59,"tag":135,"props":561,"children":562},{"class":137,"line":343},[563],{"type":59,"tag":135,"props":564,"children":565},{},[566],{"type":68,"value":567},"def webhooks():\n",{"type":59,"tag":135,"props":569,"children":570},{"class":137,"line":381},[571],{"type":59,"tag":135,"props":572,"children":573},{},[574],{"type":68,"value":575},"    raw = request.get_data()          # raw bytes for signature checks\n",{"type":59,"tag":135,"props":577,"children":578},{"class":137,"line":406},[579],{"type":59,"tag":135,"props":580,"children":581},{},[582],{"type":68,"value":583},"    if not verify_signature(raw, request.headers):\n",{"type":59,"tag":135,"props":585,"children":586},{"class":137,"line":438},[587],{"type":59,"tag":135,"props":588,"children":589},{},[590],{"type":68,"value":591},"        return \"\", 401\n",{"type":59,"tag":135,"props":593,"children":594},{"class":137,"line":447},[595],{"type":59,"tag":135,"props":596,"children":597},{},[598],{"type":68,"value":599},"    enqueue(raw)\n",{"type":59,"tag":135,"props":601,"children":602},{"class":137,"line":455},[603],{"type":59,"tag":135,"props":604,"children":605},{},[606],{"type":68,"value":607},"    return \"\", 204\n",{"type":59,"tag":135,"props":609,"children":611},{"class":137,"line":610},12,[612],{"type":59,"tag":135,"props":613,"children":614},{"emptyLinePlaceholder":200},[615],{"type":68,"value":203},{"type":59,"tag":135,"props":617,"children":619},{"class":137,"line":618},13,[620],{"type":59,"tag":135,"props":621,"children":622},{},[623],{"type":68,"value":624},"app.run(port=3000)\n",{"type":59,"tag":113,"props":626,"children":628},{"id":627},"how-a-webhook-listener-works",[629],{"type":68,"value":630},"How a webhook listener works",{"type":59,"tag":632,"props":633,"children":634},"ol",{},[635,653,685,710,743],{"type":59,"tag":636,"props":637,"children":638},"li",{},[639,644,646,651],{"type":59,"tag":64,"props":640,"children":641},{},[642],{"type":68,"value":643},"It listens.",{"type":68,"value":645}," The process binds to a port, such as ",{"type":59,"tag":73,"props":647,"children":649},{"className":648},[],[650],{"type":68,"value":474},{"type":68,"value":652},", and waits for HTTP connections.",{"type":59,"tag":636,"props":654,"children":655},{},[656,661,663,668,670,676,678,683],{"type":59,"tag":64,"props":657,"children":658},{},[659],{"type":68,"value":660},"A provider sends a request.",{"type":68,"value":662}," When an event happens, the provider sends an HTTPS ",{"type":59,"tag":73,"props":664,"children":666},{"className":665},[],[667],{"type":68,"value":78},{"type":68,"value":669}," to the public URL, and a ",{"type":59,"tag":98,"props":671,"children":673},{"href":672},"/glossary/reverse-proxy/",[674],{"type":68,"value":675},"reverse proxy",{"type":68,"value":677},", load balancer or ",{"type":59,"tag":98,"props":679,"children":680},{"href":35},[681],{"type":68,"value":682},"tunnel",{"type":68,"value":684}," passes it to the listener.",{"type":59,"tag":636,"props":686,"children":687},{},[688,693,695,701,703,709],{"type":59,"tag":64,"props":689,"children":690},{},[691],{"type":68,"value":692},"It verifies the request.",{"type":68,"value":694}," It checks the ",{"type":59,"tag":98,"props":696,"children":698},{"href":697},"/glossary/webhook-signature/",[699],{"type":68,"value":700},"webhook signature",{"type":68,"value":702}," against the raw body using the ",{"type":59,"tag":98,"props":704,"children":706},{"href":705},"/glossary/webhook-secret/",[707],{"type":68,"value":708},"webhook secret",{"type":68,"value":368},{"type":59,"tag":636,"props":711,"children":712},{},[713,718,720,726,728,734,736,741],{"type":59,"tag":64,"props":714,"children":715},{},[716],{"type":68,"value":717},"It acknowledges.",{"type":68,"value":719}," It stores or queues the ",{"type":59,"tag":98,"props":721,"children":723},{"href":722},"/glossary/webhook-payload/",[724],{"type":68,"value":725},"payload",{"type":68,"value":727}," and returns ",{"type":59,"tag":73,"props":729,"children":731},{"className":730},[],[732],{"type":68,"value":733},"200",{"type":68,"value":735}," or ",{"type":59,"tag":73,"props":737,"children":739},{"className":738},[],[740],{"type":68,"value":425},{"type":68,"value":742}," within a few seconds.",{"type":59,"tag":636,"props":744,"children":745},{},[746,751],{"type":59,"tag":64,"props":747,"children":748},{},[749],{"type":68,"value":750},"A worker processes the event.",{"type":68,"value":752}," Slow work like sending email or updating several systems happens outside the request.",{"type":59,"tag":113,"props":754,"children":756},{"id":755},"listener-vs-endpoint-vs-url",[757],{"type":68,"value":758},"Listener vs endpoint vs URL",{"type":59,"tag":760,"props":761,"children":762},"table",{},[763,787],{"type":59,"tag":764,"props":765,"children":766},"thead",{},[767],{"type":59,"tag":768,"props":769,"children":770},"tr",{},[771,777,782],{"type":59,"tag":772,"props":773,"children":774},"th",{},[775],{"type":68,"value":776},"Term",{"type":59,"tag":772,"props":778,"children":779},{},[780],{"type":68,"value":781},"What it is",{"type":59,"tag":772,"props":783,"children":784},{},[785],{"type":68,"value":786},"Example",{"type":59,"tag":788,"props":789,"children":790},"tbody",{},[791,813,834],{"type":59,"tag":768,"props":792,"children":793},{},[794,799,804],{"type":59,"tag":795,"props":796,"children":797},"td",{},[798],{"type":68,"value":29},{"type":59,"tag":795,"props":800,"children":801},{},[802],{"type":68,"value":803},"The public address given to the provider",{"type":59,"tag":795,"props":805,"children":806},{},[807],{"type":59,"tag":73,"props":808,"children":810},{"className":809},[],[811],{"type":68,"value":812},"https://api.example.com/webhooks",{"type":59,"tag":768,"props":814,"children":815},{},[816,820,825],{"type":59,"tag":795,"props":817,"children":818},{},[819],{"type":68,"value":26},{"type":59,"tag":795,"props":821,"children":822},{},[823],{"type":68,"value":824},"The route that handles it",{"type":59,"tag":795,"props":826,"children":827},{},[828],{"type":59,"tag":73,"props":829,"children":831},{"className":830},[],[832],{"type":68,"value":833},"POST /webhooks",{"type":59,"tag":768,"props":835,"children":836},{},[837,841,846],{"type":59,"tag":795,"props":838,"children":839},{},[840],{"type":68,"value":11},{"type":59,"tag":795,"props":842,"children":843},{},[844],{"type":68,"value":845},"The running server process",{"type":59,"tag":795,"props":847,"children":848},{},[849,851],{"type":68,"value":850},"A Node process on port ",{"type":59,"tag":73,"props":852,"children":854},{"className":853},[],[855],{"type":68,"value":474},{"type":59,"tag":60,"props":857,"children":858},{},[859,861,867],{"type":68,"value":860},"Some CLIs use \"listen\" for a forwarder: ",{"type":59,"tag":73,"props":862,"children":864},{"className":863},[],[865],{"type":68,"value":866},"stripe listen",{"type":68,"value":868}," receives Stripe events and forwards them to your local listener. That's a forwarding tool, not the listener itself.",{"type":59,"tag":113,"props":870,"children":872},{"id":871},"running-a-listener-on-localhost",[873],{"type":68,"value":874},"Running a listener on localhost",{"type":59,"tag":60,"props":876,"children":877},{},[878,880,888],{"type":68,"value":879},"A listener on ",{"type":59,"tag":98,"props":881,"children":882},{"href":38},[883],{"type":59,"tag":73,"props":884,"children":886},{"className":885},[],[887],{"type":68,"value":37},{"type":68,"value":889}," works for local requests, but providers can't reach it from the internet. You have three options:",{"type":59,"tag":891,"props":892,"children":893},"ul",{},[894,904,919],{"type":59,"tag":636,"props":895,"children":896},{},[897,902],{"type":59,"tag":64,"props":898,"children":899},{},[900],{"type":68,"value":901},"Deploy it",{"type":68,"value":903}," to a server with a public HTTPS address.",{"type":59,"tag":636,"props":905,"children":906},{},[907,912,914,918],{"type":59,"tag":64,"props":908,"children":909},{},[910],{"type":68,"value":911},"Open a tunnel",{"type":68,"value":913}," to it, which exposes the whole port publicly. See ",{"type":59,"tag":98,"props":915,"children":916},{"href":35},[917],{"type":68,"value":682},{"type":68,"value":368},{"type":59,"tag":636,"props":920,"children":921},{},[922,927,929,934],{"type":59,"tag":64,"props":923,"children":924},{},[925],{"type":68,"value":926},"Forward webhooks to it.",{"type":68,"value":928}," With ",{"type":59,"tag":98,"props":930,"children":931},{"href":32},[932],{"type":68,"value":933},"webhook forwarding",{"type":68,"value":935},", only the webhooks reach your machine:",{"type":59,"tag":125,"props":937,"children":941},{"className":938,"code":939,"language":940,"meta":7,"style":7},"language-bash shiki shiki-themes github-dark","relay forward --bucket my-app http://localhost:3000/webhooks\n","bash",[942],{"type":59,"tag":73,"props":943,"children":944},{"__ignoreMap":7},[945],{"type":59,"tag":135,"props":946,"children":947},{"class":137,"line":138},[948,953,958,963,968],{"type":59,"tag":135,"props":949,"children":950},{"style":185},[951],{"type":68,"value":952},"relay",{"type":59,"tag":135,"props":954,"children":955},{"style":159},[956],{"type":68,"value":957}," forward",{"type":59,"tag":135,"props":959,"children":960},{"style":174},[961],{"type":68,"value":962}," --bucket",{"type":59,"tag":135,"props":964,"children":965},{"style":159},[966],{"type":68,"value":967}," my-app",{"type":59,"tag":135,"props":969,"children":970},{"style":159},[971],{"type":68,"value":972}," http://localhost:3000/webhooks\n",{"type":59,"tag":60,"props":974,"children":975},{},[976,978,983],{"type":68,"value":977},"The bucket's URL is permanent, so you configure the provider once. Details are in ",{"type":59,"tag":98,"props":979,"children":980},{"href":20},[981],{"type":68,"value":982},"receiving webhooks on localhost",{"type":68,"value":368},{"type":59,"tag":113,"props":985,"children":987},{"id":986},"common-mistakes",[988],{"type":68,"value":989},"Common mistakes",{"type":59,"tag":891,"props":991,"children":992},{},[993,1003,1029,1046,1079],{"type":59,"tag":636,"props":994,"children":995},{},[996,1001],{"type":59,"tag":64,"props":997,"children":998},{},[999],{"type":68,"value":1000},"Parsing the body before verifying it.",{"type":68,"value":1002}," Body-parsing middleware changes the bytes and breaks signature checks. Read the raw body on webhook routes.",{"type":59,"tag":636,"props":1004,"children":1005},{},[1006,1011,1013,1019,1021,1027],{"type":59,"tag":64,"props":1007,"children":1008},{},[1009],{"type":68,"value":1010},"Doing slow work in the request.",{"type":68,"value":1012}," If the listener takes too long, the provider times out, often seen as a ",{"type":59,"tag":98,"props":1014,"children":1016},{"href":1015},"/http-errors/504-gateway-timeout/",[1017],{"type":68,"value":1018},"504 Gateway Timeout",{"type":68,"value":1020},", and ",{"type":59,"tag":98,"props":1022,"children":1024},{"href":1023},"/glossary/webhook-retries/",[1025],{"type":68,"value":1026},"retries",{"type":68,"value":1028},", so the same event is processed twice.",{"type":59,"tag":636,"props":1030,"children":1031},{},[1032,1037,1039,1045],{"type":59,"tag":64,"props":1033,"children":1034},{},[1035],{"type":68,"value":1036},"Assuming each event arrives once.",{"type":68,"value":1038}," Retries mean duplicates. Deduplicate on the event ID. See ",{"type":59,"tag":98,"props":1040,"children":1042},{"href":1041},"/glossary/idempotency/",[1043],{"type":68,"value":1044},"idempotency",{"type":68,"value":368},{"type":59,"tag":636,"props":1047,"children":1048},{},[1049,1062,1064,1069,1071,1077],{"type":59,"tag":64,"props":1050,"children":1051},{},[1052,1054,1060],{"type":68,"value":1053},"Binding to ",{"type":59,"tag":73,"props":1055,"children":1057},{"className":1056},[],[1058],{"type":68,"value":1059},"127.0.0.1",{"type":68,"value":1061}," inside a container.",{"type":68,"value":1063}," A listener in Docker that binds to ",{"type":59,"tag":73,"props":1065,"children":1067},{"className":1066},[],[1068],{"type":68,"value":1059},{"type":68,"value":1070}," can't be reached from outside the container. Bind to ",{"type":59,"tag":73,"props":1072,"children":1074},{"className":1073},[],[1075],{"type":68,"value":1076},"0.0.0.0",{"type":68,"value":1078}," and publish the port.",{"type":59,"tag":636,"props":1080,"children":1081},{},[1082,1087,1089,1095],{"type":59,"tag":64,"props":1083,"children":1084},{},[1085],{"type":68,"value":1086},"No plan for downtime.",{"type":68,"value":1088}," Deploys and crashes happen. If the provider's retry window is short, events sent while the listener was down are lost. A gateway with ",{"type":59,"tag":98,"props":1090,"children":1092},{"href":1091},"/features/durable-retries/",[1093],{"type":68,"value":1094},"durable retries",{"type":68,"value":1096}," stores them and delivers once the listener is back.",{"type":59,"tag":1098,"props":1099,"children":1100},"style",{},[1101],{"type":68,"value":1102},"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":196,"depth":196,"links":1104},[1105,1106,1107,1108,1109],{"id":115,"depth":165,"text":118},{"id":627,"depth":165,"text":630},{"id":755,"depth":165,"text":758},{"id":871,"depth":165,"text":874},{"id":986,"depth":165,"text":989},"markdown","content:glossary:webhook-listener.md","content","glossary/webhook-listener.md","glossary/webhook-listener","md",{"loc":4},1793199804942]