[{"data":1,"prerenderedAt":627},["ShallowReactive",2],{"content-query-pkxt0GZoEc":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":43,"body":55,"_type":620,"_id":621,"_source":622,"_file":623,"_stem":624,"_extension":625,"sitemap":626},"/glossary/webhook-endpoint","glossary",false,"","What is a webhook endpoint?","A webhook endpoint is the URL on your server that receives webhook HTTP requests from another service. How it works, an example handler, and what makes a good one.","reference","Webhook endpoint","The URL on your server that receives webhook requests.","Basics",1,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay","Get a webhook endpoint in seconds, even on localhost","Webhook Bin gives you a free public endpoint URL that records every request, with no signup. When you're ready to build, Webhook Relay gives you a permanent endpoint that forwards to localhost, a private network or any server, with retries and logs.","/webhook-bin/","Open Webhook Bin","/webhook-gateway/","Webhook forwarding",[25,28,31,34,37,40],{"title":26,"to":27},"Webhook URL","/glossary/webhook-url/",{"title":29,"to":30},"Callback URL","/glossary/callback-url/",{"title":32,"to":33},"Webhook payload","/glossary/webhook-payload/",{"title":35,"to":36},"Webhook signature","/glossary/webhook-signature/",{"title":38,"to":39},"What is a webhook?","/blog/what-is-webhook/",{"title":41,"to":42},"localhost:3000","/localhost/3000/",[44,46,49,52],{"q":8,"a":45},"A webhook endpoint is an HTTP URL on your server that another service sends webhook requests to. When an event happens, such as a payment succeeding or code being pushed, the provider sends an HTTP POST with the event data to that URL, and your code at the endpoint processes it.",{"q":47,"a":48},"What is the difference between a webhook endpoint and a webhook URL?","They are usually the same thing. The webhook URL is the address you paste into the provider's settings, and the webhook endpoint is the route on your server that handles requests sent to that address. People use the two terms interchangeably.",{"q":50,"a":51},"What should a webhook endpoint return?","A 2xx status code, usually 200 or 204, as quickly as possible, ideally within a few seconds. Do slow work asynchronously after responding. A non-2xx response or a timeout tells the provider the delivery failed, and most providers will retry.",{"q":53,"a":54},"Can a webhook endpoint be on localhost?","Not directly. The provider sends requests over the internet and can't reach localhost or a private network. Use a forwarding tool such as the relay CLI, which gives you a public URL and delivers each webhook to your local endpoint over an outbound connection.",{"type":56,"children":57,"toc":614},"root",[58,89,98,105,178,184,458,464,575,581,608],{"type":59,"tag":60,"props":61,"children":62},"element","p",{},[63,70,72,79,81,87],{"type":59,"tag":64,"props":65,"children":66},"strong",{},[67],{"type":68,"value":69},"text","A webhook endpoint is the URL on your server that receives webhook requests from another service.",{"type":68,"value":71}," You register it with a provider such as Stripe, GitHub or Shopify. When an event happens there, the provider sends an HTTP ",{"type":59,"tag":73,"props":74,"children":76},"code",{"className":75},[],[77],{"type":68,"value":78},"POST",{"type":68,"value":80}," with the event details to your endpoint, and your code processes it and replies with a ",{"type":59,"tag":73,"props":82,"children":84},{"className":83},[],[85],{"type":68,"value":86},"2xx",{"type":68,"value":88}," status.",{"type":59,"tag":90,"props":91,"children":93},"pre",{"code":92},"https://api.example.com/webhooks/stripe\n└────────── host ──────┘└──── path ────┘\n",[94],{"type":59,"tag":73,"props":95,"children":96},{"__ignoreMap":7},[97],{"type":68,"value":92},{"type":59,"tag":99,"props":100,"children":102},"h2",{"id":101},"how-a-webhook-endpoint-works",[103],{"type":68,"value":104},"How a webhook endpoint works",{"type":59,"tag":106,"props":107,"children":108},"ol",{},[109,123,135,153,165],{"type":59,"tag":110,"props":111,"children":112},"li",{},[113,115,121],{"type":68,"value":114},"You create a route in your app, for example ",{"type":59,"tag":73,"props":116,"children":118},{"className":117},[],[119],{"type":68,"value":120},"POST /webhooks/stripe",{"type":68,"value":122},".",{"type":59,"tag":110,"props":124,"children":125},{},[126,128,134],{"type":68,"value":127},"You give its full public URL to the provider. That's the ",{"type":59,"tag":129,"props":130,"children":131},"a",{"href":27},[132],{"type":68,"value":133},"webhook URL",{"type":68,"value":122},{"type":59,"tag":110,"props":136,"children":137},{},[138,140,145,147,152],{"type":68,"value":139},"An event fires. The provider sends a request whose body is the ",{"type":59,"tag":129,"props":141,"children":142},{"href":33},[143],{"type":68,"value":144},"webhook payload",{"type":68,"value":146},", usually JSON, along with headers that include a ",{"type":59,"tag":129,"props":148,"children":149},{"href":36},[150],{"type":68,"value":151},"signature",{"type":68,"value":122},{"type":59,"tag":110,"props":154,"children":155},{},[156,158,164],{"type":68,"value":157},"Your endpoint verifies the signature, stores or queues the event, and returns ",{"type":59,"tag":73,"props":159,"children":161},{"className":160},[],[162],{"type":68,"value":163},"200 OK",{"type":68,"value":122},{"type":59,"tag":110,"props":166,"children":167},{},[168,170,176],{"type":68,"value":169},"If the endpoint returns an error or times out, the provider ",{"type":59,"tag":129,"props":171,"children":173},{"href":172},"/glossary/webhook-retries/",[174],{"type":68,"value":175},"retries",{"type":68,"value":177}," later.",{"type":59,"tag":99,"props":179,"children":181},{"id":180},"example-endpoint",[182],{"type":68,"value":183},"Example endpoint",{"type":59,"tag":90,"props":185,"children":189},{"code":186,"language":187,"meta":7,"className":188,"style":7},"// Express\nconst raw = express.raw({ type: 'application/json' })\n\napp.post('/webhooks/stripe', raw, (req, res) => {\n  // reject the request if the signature is invalid\n  const sig = req.headers['stripe-signature']\n  const event = verifySignature(req.body, sig)\n  queue.add(event)     // do slow work later\n  res.sendStatus(200)  // acknowledge fast\n})\n","js","language-js shiki shiki-themes github-dark",[190],{"type":59,"tag":73,"props":191,"children":192},{"__ignoreMap":7},[193,204,253,263,323,332,365,392,416,449],{"type":59,"tag":194,"props":195,"children":197},"span",{"class":196,"line":14},"line",[198],{"type":59,"tag":194,"props":199,"children":201},{"style":200},"--shiki-default:#6A737D",[202],{"type":68,"value":203},"// Express\n",{"type":59,"tag":194,"props":205,"children":207},{"class":196,"line":206},2,[208,214,220,225,231,237,242,248],{"type":59,"tag":194,"props":209,"children":211},{"style":210},"--shiki-default:#F97583",[212],{"type":68,"value":213},"const",{"type":59,"tag":194,"props":215,"children":217},{"style":216},"--shiki-default:#79B8FF",[218],{"type":68,"value":219}," raw",{"type":59,"tag":194,"props":221,"children":222},{"style":210},[223],{"type":68,"value":224}," =",{"type":59,"tag":194,"props":226,"children":228},{"style":227},"--shiki-default:#E1E4E8",[229],{"type":68,"value":230}," express.",{"type":59,"tag":194,"props":232,"children":234},{"style":233},"--shiki-default:#B392F0",[235],{"type":68,"value":236},"raw",{"type":59,"tag":194,"props":238,"children":239},{"style":227},[240],{"type":68,"value":241},"({ type: ",{"type":59,"tag":194,"props":243,"children":245},{"style":244},"--shiki-default:#9ECBFF",[246],{"type":68,"value":247},"'application/json'",{"type":59,"tag":194,"props":249,"children":250},{"style":227},[251],{"type":68,"value":252}," })\n",{"type":59,"tag":194,"props":254,"children":256},{"class":196,"line":255},3,[257],{"type":59,"tag":194,"props":258,"children":260},{"emptyLinePlaceholder":259},true,[261],{"type":68,"value":262},"\n",{"type":59,"tag":194,"props":264,"children":266},{"class":196,"line":265},4,[267,272,277,282,287,292,298,303,308,313,318],{"type":59,"tag":194,"props":268,"children":269},{"style":227},[270],{"type":68,"value":271},"app.",{"type":59,"tag":194,"props":273,"children":274},{"style":233},[275],{"type":68,"value":276},"post",{"type":59,"tag":194,"props":278,"children":279},{"style":227},[280],{"type":68,"value":281},"(",{"type":59,"tag":194,"props":283,"children":284},{"style":244},[285],{"type":68,"value":286},"'/webhooks/stripe'",{"type":59,"tag":194,"props":288,"children":289},{"style":227},[290],{"type":68,"value":291},", raw, (",{"type":59,"tag":194,"props":293,"children":295},{"style":294},"--shiki-default:#FFAB70",[296],{"type":68,"value":297},"req",{"type":59,"tag":194,"props":299,"children":300},{"style":227},[301],{"type":68,"value":302},", ",{"type":59,"tag":194,"props":304,"children":305},{"style":294},[306],{"type":68,"value":307},"res",{"type":59,"tag":194,"props":309,"children":310},{"style":227},[311],{"type":68,"value":312},") ",{"type":59,"tag":194,"props":314,"children":315},{"style":210},[316],{"type":68,"value":317},"=>",{"type":59,"tag":194,"props":319,"children":320},{"style":227},[321],{"type":68,"value":322}," {\n",{"type":59,"tag":194,"props":324,"children":326},{"class":196,"line":325},5,[327],{"type":59,"tag":194,"props":328,"children":329},{"style":200},[330],{"type":68,"value":331},"  // reject the request if the signature is invalid\n",{"type":59,"tag":194,"props":333,"children":335},{"class":196,"line":334},6,[336,341,346,350,355,360],{"type":59,"tag":194,"props":337,"children":338},{"style":210},[339],{"type":68,"value":340},"  const",{"type":59,"tag":194,"props":342,"children":343},{"style":216},[344],{"type":68,"value":345}," sig",{"type":59,"tag":194,"props":347,"children":348},{"style":210},[349],{"type":68,"value":224},{"type":59,"tag":194,"props":351,"children":352},{"style":227},[353],{"type":68,"value":354}," req.headers[",{"type":59,"tag":194,"props":356,"children":357},{"style":244},[358],{"type":68,"value":359},"'stripe-signature'",{"type":59,"tag":194,"props":361,"children":362},{"style":227},[363],{"type":68,"value":364},"]\n",{"type":59,"tag":194,"props":366,"children":368},{"class":196,"line":367},7,[369,373,378,382,387],{"type":59,"tag":194,"props":370,"children":371},{"style":210},[372],{"type":68,"value":340},{"type":59,"tag":194,"props":374,"children":375},{"style":216},[376],{"type":68,"value":377}," event",{"type":59,"tag":194,"props":379,"children":380},{"style":210},[381],{"type":68,"value":224},{"type":59,"tag":194,"props":383,"children":384},{"style":233},[385],{"type":68,"value":386}," verifySignature",{"type":59,"tag":194,"props":388,"children":389},{"style":227},[390],{"type":68,"value":391},"(req.body, sig)\n",{"type":59,"tag":194,"props":393,"children":395},{"class":196,"line":394},8,[396,401,406,411],{"type":59,"tag":194,"props":397,"children":398},{"style":227},[399],{"type":68,"value":400},"  queue.",{"type":59,"tag":194,"props":402,"children":403},{"style":233},[404],{"type":68,"value":405},"add",{"type":59,"tag":194,"props":407,"children":408},{"style":227},[409],{"type":68,"value":410},"(event)     ",{"type":59,"tag":194,"props":412,"children":413},{"style":200},[414],{"type":68,"value":415},"// do slow work later\n",{"type":59,"tag":194,"props":417,"children":419},{"class":196,"line":418},9,[420,425,430,434,439,444],{"type":59,"tag":194,"props":421,"children":422},{"style":227},[423],{"type":68,"value":424},"  res.",{"type":59,"tag":194,"props":426,"children":427},{"style":233},[428],{"type":68,"value":429},"sendStatus",{"type":59,"tag":194,"props":431,"children":432},{"style":227},[433],{"type":68,"value":281},{"type":59,"tag":194,"props":435,"children":436},{"style":216},[437],{"type":68,"value":438},"200",{"type":59,"tag":194,"props":440,"children":441},{"style":227},[442],{"type":68,"value":443},")  ",{"type":59,"tag":194,"props":445,"children":446},{"style":200},[447],{"type":68,"value":448},"// acknowledge fast\n",{"type":59,"tag":194,"props":450,"children":452},{"class":196,"line":451},10,[453],{"type":59,"tag":194,"props":454,"children":455},{"style":227},[456],{"type":68,"value":457},"})\n",{"type":59,"tag":99,"props":459,"children":461},{"id":460},"what-makes-a-good-webhook-endpoint",[462],{"type":68,"value":463},"What makes a good webhook endpoint",{"type":59,"tag":465,"props":466,"children":467},"ul",{},[468,498,516,548,565],{"type":59,"tag":110,"props":469,"children":470},{},[471,476,478,484,486,496],{"type":59,"tag":64,"props":472,"children":473},{},[474],{"type":68,"value":475},"Publicly reachable over HTTPS.",{"type":68,"value":477}," Providers can't reach ",{"type":59,"tag":73,"props":479,"children":481},{"className":480},[],[482],{"type":68,"value":483},"localhost",{"type":68,"value":485}," or a private network. Many refuse plain HTTP or self-signed certificates (",{"type":59,"tag":129,"props":487,"children":489},{"href":488},"/http-errors/err-cert-authority-invalid/",[490],{"type":59,"tag":73,"props":491,"children":493},{"className":492},[],[494],{"type":68,"value":495},"ERR_CERT_AUTHORITY_INVALID",{"type":68,"value":497},").",{"type":59,"tag":110,"props":499,"children":500},{},[501,506,508,514],{"type":59,"tag":64,"props":502,"children":503},{},[504],{"type":68,"value":505},"Fast.",{"type":68,"value":507}," Respond in a few seconds and do the heavy lifting asynchronously. Slow handlers cause ",{"type":59,"tag":129,"props":509,"children":511},{"href":510},"/http-errors/504-gateway-timeout/",[512],{"type":68,"value":513},"504 timeouts",{"type":68,"value":515}," and duplicate retries.",{"type":59,"tag":110,"props":517,"children":518},{},[519,524,526,531,533,539,541,547],{"type":59,"tag":64,"props":520,"children":521},{},[522],{"type":68,"value":523},"Verified.",{"type":68,"value":525}," Check the ",{"type":59,"tag":129,"props":527,"children":528},{"href":36},[529],{"type":68,"value":530},"webhook signature",{"type":68,"value":532}," with your ",{"type":59,"tag":129,"props":534,"children":536},{"href":535},"/glossary/webhook-secret/",[537],{"type":68,"value":538},"webhook secret",{"type":68,"value":540}," before trusting the body. You can test one with the ",{"type":59,"tag":129,"props":542,"children":544},{"href":543},"/verify-webhook-signature/",[545],{"type":68,"value":546},"signature verifier",{"type":68,"value":122},{"type":59,"tag":110,"props":549,"children":550},{},[551,556,558,564],{"type":59,"tag":64,"props":552,"children":553},{},[554],{"type":68,"value":555},"Idempotent.",{"type":68,"value":557}," The same event can arrive more than once, so deduplicate on the event ID. See ",{"type":59,"tag":129,"props":559,"children":561},{"href":560},"/glossary/idempotency/",[562],{"type":68,"value":563},"idempotency",{"type":68,"value":122},{"type":59,"tag":110,"props":566,"children":567},{},[568,573],{"type":59,"tag":64,"props":569,"children":570},{},[571],{"type":68,"value":572},"Observable.",{"type":68,"value":574}," Log every request so you can see what the provider actually sent and replay it.",{"type":59,"tag":99,"props":576,"children":578},{"id":577},"testing-a-webhook-endpoint",[579],{"type":68,"value":580},"Testing a webhook endpoint",{"type":59,"tag":60,"props":582,"children":583},{},[584,586,591,593,598,600,606],{"type":68,"value":585},"To see what a provider sends before writing any code, point it at a free ",{"type":59,"tag":129,"props":587,"children":588},{"href":20},[589],{"type":68,"value":590},"Webhook Bin",{"type":68,"value":592}," URL and inspect the captured requests. To test your real handler on ",{"type":59,"tag":73,"props":594,"children":596},{"className":595},[],[597],{"type":68,"value":483},{"type":68,"value":599},", run ",{"type":59,"tag":73,"props":601,"children":603},{"className":602},[],[604],{"type":68,"value":605},"relay forward --bucket my-app http://localhost:3000/webhooks",{"type":68,"value":607},". You get a permanent public URL, and each webhook is delivered to your machine.",{"type":59,"tag":609,"props":610,"children":611},"style",{},[612],{"type":68,"value":613},"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":255,"depth":255,"links":615},[616,617,618,619],{"id":101,"depth":206,"text":104},{"id":180,"depth":206,"text":183},{"id":460,"depth":206,"text":463},{"id":577,"depth":206,"text":580},"markdown","content:glossary:webhook-endpoint.md","content","glossary/webhook-endpoint.md","glossary/webhook-endpoint","md",{"loc":4},1793199804283]