[{"data":1,"prerenderedAt":1999},["ShallowReactive",2],{"search-de":3,"content-de-examples\u002Fwebhook-handler":4,"surround-de-\u002Fexamples\u002Fwebhook-handler":1990},[],{"id":5,"title":6,"body":7,"description":13,"extension":1984,"meta":1985,"navigation":94,"path":1986,"seo":1987,"stem":1988,"__hash__":1989},"content_de\u002F4.examples\u002F5.webhook-handler.md","Webhook-Handler",{"type":8,"value":9,"toc":1975},"minimark",[10,14,19,43,47,712,723,727,730,1332,1355,1359,1362,1567,1573,1577,1580,1743,1774,1778,1790,1865,1868,1936,1940,1971],[11,12,13],"p",{},"Verarbeiten Sie Owlats Zustell-Webhooks mit Signaturprüfung und Event-Routing.",[15,16,18],"h2",{"id":17},"voraussetzungen","Voraussetzungen",[20,21,22,36],"ul",{},[23,24,25,26,30,31,35],"li",{},"Ein unter ",[27,28,29],"strong",{},"Einstellungen → Webhooks"," konfigurierter Webhook mit einem ",[32,33,34],"code",{},"whsec_...","-Secret",[23,37,38,39,42],{},"Ein öffentlich erreichbarer Endpunkt, der ",[32,40,41],{},"POST","-Anfragen annimmt",[15,44,46],{"id":45},"vollständiger-express-handler","Vollständiger Express-Handler",[48,49,54],"pre",{"className":50,"code":51,"language":52,"meta":53,"style":53},"language-typescript shiki shiki-themes github-light github-dark-dimmed","import express from 'express'\nimport crypto from 'crypto'\n\nconst app = express()\n\n\u002F\u002F Use raw body for signature verification\napp.post('\u002Fwebhooks\u002Fowlat', express.raw({ type: 'application\u002Fjson' }), async (req, res) => {\n  const payload = req.body.toString()\n  const signature = req.headers['x-signature'] as string\n  const timestamp = req.headers['x-timestamp'] as string\n\n  \u002F\u002F 1. Verify signature\n  if (!verifySignature(payload, signature, process.env.OWLAT_WEBHOOK_SECRET!)) {\n    return res.status(401).json({ error: 'Invalid signature' })\n  }\n\n  \u002F\u002F 2. Check timestamp freshness (reject requests older than 5 minutes)\n  const age = Math.abs(Date.now() \u002F 1000 - Number(timestamp))\n  if (age > 300) {\n    return res.status(401).json({ error: 'Request too old' })\n  }\n\n  \u002F\u002F 3. Return 200 immediately — process async\n  res.status(200).json({ received: true })\n\n  \u002F\u002F 4. Route events\n  const event = JSON.parse(payload)\n  await handleEvent(event)\n})\n\nfunction verifySignature(payload: string, signature: string, secret: string): boolean {\n  const expected = crypto\n    .createHmac('sha256', secret)\n    .update(payload)\n    .digest('hex')\n  const sigBuf = Buffer.from(signature, 'hex')\n  const expBuf = Buffer.from(expected, 'hex')\n  \u002F\u002F timingSafeEqual throws on length mismatch — guard against malformed signatures\n  if (sigBuf.length !== expBuf.length) return false\n  return crypto.timingSafeEqual(sigBuf, expBuf)\n}\n","typescript","",[32,55,56,76,89,96,116,121,128,183,202,227,248,253,259,284,316,322,327,333,373,390,414,419,424,430,455,460,466,488,500,506,511,559,572,589,599,615,637,658,664,691,706],{"__ignoreMap":53},[57,58,61,65,69,72],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"s7YZ4","import",[57,66,68],{"class":67},"sYgZi"," express ",[57,70,71],{"class":63},"from",[57,73,75],{"class":74},"s-HuK"," 'express'\n",[57,77,79,81,84,86],{"class":59,"line":78},2,[57,80,64],{"class":63},[57,82,83],{"class":67}," crypto ",[57,85,71],{"class":63},[57,87,88],{"class":74}," 'crypto'\n",[57,90,92],{"class":59,"line":91},3,[57,93,95],{"emptyLinePlaceholder":94},true,"\n",[57,97,99,102,106,109,113],{"class":59,"line":98},4,[57,100,101],{"class":63},"const",[57,103,105],{"class":104},"sviXB"," app",[57,107,108],{"class":63}," =",[57,110,112],{"class":111},"sPO5f"," express",[57,114,115],{"class":67},"()\n",[57,117,119],{"class":59,"line":118},5,[57,120,95],{"emptyLinePlaceholder":94},[57,122,124],{"class":59,"line":123},6,[57,125,127],{"class":126},"sDN9O","\u002F\u002F Use raw body for signature verification\n",[57,129,131,134,137,140,143,146,149,152,155,158,161,164,168,171,174,177,180],{"class":59,"line":130},7,[57,132,133],{"class":67},"app.",[57,135,136],{"class":111},"post",[57,138,139],{"class":67},"(",[57,141,142],{"class":74},"'\u002Fwebhooks\u002Fowlat'",[57,144,145],{"class":67},", express.",[57,147,148],{"class":111},"raw",[57,150,151],{"class":67},"({ type: ",[57,153,154],{"class":74},"'application\u002Fjson'",[57,156,157],{"class":67}," }), ",[57,159,160],{"class":63},"async",[57,162,163],{"class":67}," (",[57,165,167],{"class":166},"stnAF","req",[57,169,170],{"class":67},", ",[57,172,173],{"class":166},"res",[57,175,176],{"class":67},") ",[57,178,179],{"class":63},"=>",[57,181,182],{"class":67}," {\n",[57,184,186,189,192,194,197,200],{"class":59,"line":185},8,[57,187,188],{"class":63},"  const",[57,190,191],{"class":104}," payload",[57,193,108],{"class":63},[57,195,196],{"class":67}," req.body.",[57,198,199],{"class":111},"toString",[57,201,115],{"class":67},[57,203,205,207,210,212,215,218,221,224],{"class":59,"line":204},9,[57,206,188],{"class":63},[57,208,209],{"class":104}," signature",[57,211,108],{"class":63},[57,213,214],{"class":67}," req.headers[",[57,216,217],{"class":74},"'x-signature'",[57,219,220],{"class":67},"] ",[57,222,223],{"class":63},"as",[57,225,226],{"class":104}," string\n",[57,228,230,232,235,237,239,242,244,246],{"class":59,"line":229},10,[57,231,188],{"class":63},[57,233,234],{"class":104}," timestamp",[57,236,108],{"class":63},[57,238,214],{"class":67},[57,240,241],{"class":74},"'x-timestamp'",[57,243,220],{"class":67},[57,245,223],{"class":63},[57,247,226],{"class":104},[57,249,251],{"class":59,"line":250},11,[57,252,95],{"emptyLinePlaceholder":94},[57,254,256],{"class":59,"line":255},12,[57,257,258],{"class":126},"  \u002F\u002F 1. Verify signature\n",[57,260,262,265,267,270,273,276,279,281],{"class":59,"line":261},13,[57,263,264],{"class":63},"  if",[57,266,163],{"class":67},[57,268,269],{"class":63},"!",[57,271,272],{"class":111},"verifySignature",[57,274,275],{"class":67},"(payload, signature, process.env.",[57,277,278],{"class":104},"OWLAT_WEBHOOK_SECRET",[57,280,269],{"class":63},[57,282,283],{"class":67},")) {\n",[57,285,287,290,293,296,298,301,304,307,310,313],{"class":59,"line":286},14,[57,288,289],{"class":63},"    return",[57,291,292],{"class":67}," res.",[57,294,295],{"class":111},"status",[57,297,139],{"class":67},[57,299,300],{"class":104},"401",[57,302,303],{"class":67},").",[57,305,306],{"class":111},"json",[57,308,309],{"class":67},"({ error: ",[57,311,312],{"class":74},"'Invalid signature'",[57,314,315],{"class":67}," })\n",[57,317,319],{"class":59,"line":318},15,[57,320,321],{"class":67},"  }\n",[57,323,325],{"class":59,"line":324},16,[57,326,95],{"emptyLinePlaceholder":94},[57,328,330],{"class":59,"line":329},17,[57,331,332],{"class":126},"  \u002F\u002F 2. Check timestamp freshness (reject requests older than 5 minutes)\n",[57,334,336,338,341,343,346,349,352,355,358,361,364,367,370],{"class":59,"line":335},18,[57,337,188],{"class":63},[57,339,340],{"class":104}," age",[57,342,108],{"class":63},[57,344,345],{"class":67}," Math.",[57,347,348],{"class":111},"abs",[57,350,351],{"class":67},"(Date.",[57,353,354],{"class":111},"now",[57,356,357],{"class":67},"() ",[57,359,360],{"class":63},"\u002F",[57,362,363],{"class":104}," 1000",[57,365,366],{"class":63}," -",[57,368,369],{"class":111}," Number",[57,371,372],{"class":67},"(timestamp))\n",[57,374,376,378,381,384,387],{"class":59,"line":375},19,[57,377,264],{"class":63},[57,379,380],{"class":67}," (age ",[57,382,383],{"class":63},">",[57,385,386],{"class":104}," 300",[57,388,389],{"class":67},") {\n",[57,391,393,395,397,399,401,403,405,407,409,412],{"class":59,"line":392},20,[57,394,289],{"class":63},[57,396,292],{"class":67},[57,398,295],{"class":111},[57,400,139],{"class":67},[57,402,300],{"class":104},[57,404,303],{"class":67},[57,406,306],{"class":111},[57,408,309],{"class":67},[57,410,411],{"class":74},"'Request too old'",[57,413,315],{"class":67},[57,415,417],{"class":59,"line":416},21,[57,418,321],{"class":67},[57,420,422],{"class":59,"line":421},22,[57,423,95],{"emptyLinePlaceholder":94},[57,425,427],{"class":59,"line":426},23,[57,428,429],{"class":126},"  \u002F\u002F 3. Return 200 immediately — process async\n",[57,431,433,436,438,440,443,445,447,450,453],{"class":59,"line":432},24,[57,434,435],{"class":67},"  res.",[57,437,295],{"class":111},[57,439,139],{"class":67},[57,441,442],{"class":104},"200",[57,444,303],{"class":67},[57,446,306],{"class":111},[57,448,449],{"class":67},"({ received: ",[57,451,452],{"class":104},"true",[57,454,315],{"class":67},[57,456,458],{"class":59,"line":457},25,[57,459,95],{"emptyLinePlaceholder":94},[57,461,463],{"class":59,"line":462},26,[57,464,465],{"class":126},"  \u002F\u002F 4. Route events\n",[57,467,469,471,474,476,479,482,485],{"class":59,"line":468},27,[57,470,188],{"class":63},[57,472,473],{"class":104}," event",[57,475,108],{"class":63},[57,477,478],{"class":104}," JSON",[57,480,481],{"class":67},".",[57,483,484],{"class":111},"parse",[57,486,487],{"class":67},"(payload)\n",[57,489,491,494,497],{"class":59,"line":490},28,[57,492,493],{"class":63},"  await",[57,495,496],{"class":111}," handleEvent",[57,498,499],{"class":67},"(event)\n",[57,501,503],{"class":59,"line":502},29,[57,504,505],{"class":67},"})\n",[57,507,509],{"class":59,"line":508},30,[57,510,95],{"emptyLinePlaceholder":94},[57,512,514,517,520,522,525,528,531,533,536,538,540,542,545,547,549,552,554,557],{"class":59,"line":513},31,[57,515,516],{"class":63},"function",[57,518,519],{"class":111}," verifySignature",[57,521,139],{"class":67},[57,523,524],{"class":166},"payload",[57,526,527],{"class":63},":",[57,529,530],{"class":104}," string",[57,532,170],{"class":67},[57,534,535],{"class":166},"signature",[57,537,527],{"class":63},[57,539,530],{"class":104},[57,541,170],{"class":67},[57,543,544],{"class":166},"secret",[57,546,527],{"class":63},[57,548,530],{"class":104},[57,550,551],{"class":67},")",[57,553,527],{"class":63},[57,555,556],{"class":104}," boolean",[57,558,182],{"class":67},[57,560,562,564,567,569],{"class":59,"line":561},32,[57,563,188],{"class":63},[57,565,566],{"class":104}," expected",[57,568,108],{"class":63},[57,570,571],{"class":67}," crypto\n",[57,573,575,578,581,583,586],{"class":59,"line":574},33,[57,576,577],{"class":67},"    .",[57,579,580],{"class":111},"createHmac",[57,582,139],{"class":67},[57,584,585],{"class":74},"'sha256'",[57,587,588],{"class":67},", secret)\n",[57,590,592,594,597],{"class":59,"line":591},34,[57,593,577],{"class":67},[57,595,596],{"class":111},"update",[57,598,487],{"class":67},[57,600,602,604,607,609,612],{"class":59,"line":601},35,[57,603,577],{"class":67},[57,605,606],{"class":111},"digest",[57,608,139],{"class":67},[57,610,611],{"class":74},"'hex'",[57,613,614],{"class":67},")\n",[57,616,618,620,623,625,628,630,633,635],{"class":59,"line":617},36,[57,619,188],{"class":63},[57,621,622],{"class":104}," sigBuf",[57,624,108],{"class":63},[57,626,627],{"class":67}," Buffer.",[57,629,71],{"class":111},[57,631,632],{"class":67},"(signature, ",[57,634,611],{"class":74},[57,636,614],{"class":67},[57,638,640,642,645,647,649,651,654,656],{"class":59,"line":639},37,[57,641,188],{"class":63},[57,643,644],{"class":104}," expBuf",[57,646,108],{"class":63},[57,648,627],{"class":67},[57,650,71],{"class":111},[57,652,653],{"class":67},"(expected, ",[57,655,611],{"class":74},[57,657,614],{"class":67},[57,659,661],{"class":59,"line":660},38,[57,662,663],{"class":126},"  \u002F\u002F timingSafeEqual throws on length mismatch — guard against malformed signatures\n",[57,665,667,669,672,675,678,681,683,685,688],{"class":59,"line":666},39,[57,668,264],{"class":63},[57,670,671],{"class":67}," (sigBuf.",[57,673,674],{"class":104},"length",[57,676,677],{"class":63}," !==",[57,679,680],{"class":67}," expBuf.",[57,682,674],{"class":104},[57,684,176],{"class":67},[57,686,687],{"class":63},"return",[57,689,690],{"class":104}," false\n",[57,692,694,697,700,703],{"class":59,"line":693},40,[57,695,696],{"class":63},"  return",[57,698,699],{"class":67}," crypto.",[57,701,702],{"class":111},"timingSafeEqual",[57,704,705],{"class":67},"(sigBuf, expBuf)\n",[57,707,709],{"class":59,"line":708},41,[57,710,711],{"class":67},"}\n",[713,714,717],"callout",{"title":715,"type":716},"Schnell 200 zurückgeben","tip",[11,718,719,720,722],{},"Owlat erwartet eine Antwort innerhalb von 30 Sekunden. Geben Sie ",[32,721,442],{}," zurück, sobald Sie die Signatur geprüft haben, und verarbeiten Sie das Event anschließend asynchron. Das verhindert Wiederholungsversuche bei langsamen Handlern.",[15,724,726],{"id":725},"event-routing","Event-Routing",[11,728,729],{},"Routen Sie Events nach Typ über ein switch\u002Fcase:",[48,731,733],{"className":50,"code":732,"language":52,"meta":53,"style":53},"interface WebhookEvent {\n  event: string\n  timestamp: string\n  data: Record\u003Cstring, unknown>\n}\n\nasync function handleEvent(event: WebhookEvent) {\n  switch (event.event) {\n    case 'email.delivered':\n      console.log(`Delivered to ${event.data.email}`)\n      \u002F\u002F Outbound payloads key only on the recipient email — correlate by\n      \u002F\u002F email (and the event's own data.timestamp) rather than a message ID.\n      await db.users.update({\n        where: { email: event.data.email as string },\n        data: { lastDeliveredAt: event.data.timestamp as string },\n      })\n      break\n\n    case 'email.bounced':\n      console.log(`Bounced: ${event.data.email}`)\n      \u002F\u002F Mark the contact as undeliverable in your system\n      await db.users.update({\n        where: { email: event.data.email as string },\n        data: { emailStatus: 'bounced' },\n      })\n      break\n\n    case 'email.opened':\n      console.log(`Opened by ${event.data.email}`)\n      await db.users.update({\n        where: { email: event.data.email as string },\n        data: { lastOpenedAt: event.data.timestamp as string },\n      })\n      break\n\n    case 'email.clicked':\n      console.log(`Click from ${event.data.email}: ${event.data.url}`)\n      await db.clickEvents.create({\n        data: {\n          email: event.data.email as string,\n          url: event.data.url as string,\n          clickedAt: event.data.timestamp as string,\n        },\n      })\n      break\n\n    case 'email.complained':\n      console.log(`Complaint from ${event.data.email}`)\n      await db.users.update({\n        where: { email: event.data.email as string },\n        data: { emailStatus: 'complained', suppressEmail: true },\n      })\n      break\n\n    default:\n      console.log(`Unhandled event: ${event.event}`)\n  }\n}\n",[32,734,735,746,755,764,788,792,796,816,824,835,865,870,875,888,900,911,916,921,925,934,959,964,974,984,994,998,1002,1006,1015,1040,1050,1060,1071,1075,1079,1083,1092,1131,1143,1148,1160,1171,1183,1189,1194,1199,1204,1214,1240,1251,1262,1277,1282,1287,1292,1300,1322,1327],{"__ignoreMap":53},[57,736,737,740,744],{"class":59,"line":60},[57,738,739],{"class":63},"interface",[57,741,743],{"class":742},"sOLd2"," WebhookEvent",[57,745,182],{"class":67},[57,747,748,751,753],{"class":59,"line":78},[57,749,750],{"class":166},"  event",[57,752,527],{"class":63},[57,754,226],{"class":104},[57,756,757,760,762],{"class":59,"line":91},[57,758,759],{"class":166},"  timestamp",[57,761,527],{"class":63},[57,763,226],{"class":104},[57,765,766,769,771,774,777,780,782,785],{"class":59,"line":98},[57,767,768],{"class":166},"  data",[57,770,527],{"class":63},[57,772,773],{"class":742}," Record",[57,775,776],{"class":67},"\u003C",[57,778,779],{"class":104},"string",[57,781,170],{"class":67},[57,783,784],{"class":104},"unknown",[57,786,787],{"class":67},">\n",[57,789,790],{"class":59,"line":118},[57,791,711],{"class":67},[57,793,794],{"class":59,"line":123},[57,795,95],{"emptyLinePlaceholder":94},[57,797,798,800,803,805,807,810,812,814],{"class":59,"line":130},[57,799,160],{"class":63},[57,801,802],{"class":63}," function",[57,804,496],{"class":111},[57,806,139],{"class":67},[57,808,809],{"class":166},"event",[57,811,527],{"class":63},[57,813,743],{"class":742},[57,815,389],{"class":67},[57,817,818,821],{"class":59,"line":185},[57,819,820],{"class":63},"  switch",[57,822,823],{"class":67}," (event.event) {\n",[57,825,826,829,832],{"class":59,"line":204},[57,827,828],{"class":63},"    case",[57,830,831],{"class":74}," 'email.delivered'",[57,833,834],{"class":67},":\n",[57,836,837,840,843,845,848,850,852,855,857,860,863],{"class":59,"line":229},[57,838,839],{"class":67},"      console.",[57,841,842],{"class":111},"log",[57,844,139],{"class":67},[57,846,847],{"class":74},"`Delivered to ${",[57,849,809],{"class":67},[57,851,481],{"class":74},[57,853,854],{"class":67},"data",[57,856,481],{"class":74},[57,858,859],{"class":67},"email",[57,861,862],{"class":74},"}`",[57,864,614],{"class":67},[57,866,867],{"class":59,"line":250},[57,868,869],{"class":126},"      \u002F\u002F Outbound payloads key only on the recipient email — correlate by\n",[57,871,872],{"class":59,"line":255},[57,873,874],{"class":126},"      \u002F\u002F email (and the event's own data.timestamp) rather than a message ID.\n",[57,876,877,880,883,885],{"class":59,"line":261},[57,878,879],{"class":63},"      await",[57,881,882],{"class":67}," db.users.",[57,884,596],{"class":111},[57,886,887],{"class":67},"({\n",[57,889,890,893,895,897],{"class":59,"line":286},[57,891,892],{"class":67},"        where: { email: event.data.email ",[57,894,223],{"class":63},[57,896,530],{"class":104},[57,898,899],{"class":67}," },\n",[57,901,902,905,907,909],{"class":59,"line":318},[57,903,904],{"class":67},"        data: { lastDeliveredAt: event.data.timestamp ",[57,906,223],{"class":63},[57,908,530],{"class":104},[57,910,899],{"class":67},[57,912,913],{"class":59,"line":324},[57,914,915],{"class":67},"      })\n",[57,917,918],{"class":59,"line":329},[57,919,920],{"class":63},"      break\n",[57,922,923],{"class":59,"line":335},[57,924,95],{"emptyLinePlaceholder":94},[57,926,927,929,932],{"class":59,"line":375},[57,928,828],{"class":63},[57,930,931],{"class":74}," 'email.bounced'",[57,933,834],{"class":67},[57,935,936,938,940,942,945,947,949,951,953,955,957],{"class":59,"line":392},[57,937,839],{"class":67},[57,939,842],{"class":111},[57,941,139],{"class":67},[57,943,944],{"class":74},"`Bounced: ${",[57,946,809],{"class":67},[57,948,481],{"class":74},[57,950,854],{"class":67},[57,952,481],{"class":74},[57,954,859],{"class":67},[57,956,862],{"class":74},[57,958,614],{"class":67},[57,960,961],{"class":59,"line":416},[57,962,963],{"class":126},"      \u002F\u002F Mark the contact as undeliverable in your system\n",[57,965,966,968,970,972],{"class":59,"line":421},[57,967,879],{"class":63},[57,969,882],{"class":67},[57,971,596],{"class":111},[57,973,887],{"class":67},[57,975,976,978,980,982],{"class":59,"line":426},[57,977,892],{"class":67},[57,979,223],{"class":63},[57,981,530],{"class":104},[57,983,899],{"class":67},[57,985,986,989,992],{"class":59,"line":432},[57,987,988],{"class":67},"        data: { emailStatus: ",[57,990,991],{"class":74},"'bounced'",[57,993,899],{"class":67},[57,995,996],{"class":59,"line":457},[57,997,915],{"class":67},[57,999,1000],{"class":59,"line":462},[57,1001,920],{"class":63},[57,1003,1004],{"class":59,"line":468},[57,1005,95],{"emptyLinePlaceholder":94},[57,1007,1008,1010,1013],{"class":59,"line":490},[57,1009,828],{"class":63},[57,1011,1012],{"class":74}," 'email.opened'",[57,1014,834],{"class":67},[57,1016,1017,1019,1021,1023,1026,1028,1030,1032,1034,1036,1038],{"class":59,"line":502},[57,1018,839],{"class":67},[57,1020,842],{"class":111},[57,1022,139],{"class":67},[57,1024,1025],{"class":74},"`Opened by ${",[57,1027,809],{"class":67},[57,1029,481],{"class":74},[57,1031,854],{"class":67},[57,1033,481],{"class":74},[57,1035,859],{"class":67},[57,1037,862],{"class":74},[57,1039,614],{"class":67},[57,1041,1042,1044,1046,1048],{"class":59,"line":508},[57,1043,879],{"class":63},[57,1045,882],{"class":67},[57,1047,596],{"class":111},[57,1049,887],{"class":67},[57,1051,1052,1054,1056,1058],{"class":59,"line":513},[57,1053,892],{"class":67},[57,1055,223],{"class":63},[57,1057,530],{"class":104},[57,1059,899],{"class":67},[57,1061,1062,1065,1067,1069],{"class":59,"line":561},[57,1063,1064],{"class":67},"        data: { lastOpenedAt: event.data.timestamp ",[57,1066,223],{"class":63},[57,1068,530],{"class":104},[57,1070,899],{"class":67},[57,1072,1073],{"class":59,"line":574},[57,1074,915],{"class":67},[57,1076,1077],{"class":59,"line":591},[57,1078,920],{"class":63},[57,1080,1081],{"class":59,"line":601},[57,1082,95],{"emptyLinePlaceholder":94},[57,1084,1085,1087,1090],{"class":59,"line":617},[57,1086,828],{"class":63},[57,1088,1089],{"class":74}," 'email.clicked'",[57,1091,834],{"class":67},[57,1093,1094,1096,1098,1100,1103,1105,1107,1109,1111,1113,1116,1118,1120,1122,1124,1127,1129],{"class":59,"line":639},[57,1095,839],{"class":67},[57,1097,842],{"class":111},[57,1099,139],{"class":67},[57,1101,1102],{"class":74},"`Click from ${",[57,1104,809],{"class":67},[57,1106,481],{"class":74},[57,1108,854],{"class":67},[57,1110,481],{"class":74},[57,1112,859],{"class":67},[57,1114,1115],{"class":74},"}: ${",[57,1117,809],{"class":67},[57,1119,481],{"class":74},[57,1121,854],{"class":67},[57,1123,481],{"class":74},[57,1125,1126],{"class":67},"url",[57,1128,862],{"class":74},[57,1130,614],{"class":67},[57,1132,1133,1135,1138,1141],{"class":59,"line":660},[57,1134,879],{"class":63},[57,1136,1137],{"class":67}," db.clickEvents.",[57,1139,1140],{"class":111},"create",[57,1142,887],{"class":67},[57,1144,1145],{"class":59,"line":666},[57,1146,1147],{"class":67},"        data: {\n",[57,1149,1150,1153,1155,1157],{"class":59,"line":693},[57,1151,1152],{"class":67},"          email: event.data.email ",[57,1154,223],{"class":63},[57,1156,530],{"class":104},[57,1158,1159],{"class":67},",\n",[57,1161,1162,1165,1167,1169],{"class":59,"line":708},[57,1163,1164],{"class":67},"          url: event.data.url ",[57,1166,223],{"class":63},[57,1168,530],{"class":104},[57,1170,1159],{"class":67},[57,1172,1174,1177,1179,1181],{"class":59,"line":1173},42,[57,1175,1176],{"class":67},"          clickedAt: event.data.timestamp ",[57,1178,223],{"class":63},[57,1180,530],{"class":104},[57,1182,1159],{"class":67},[57,1184,1186],{"class":59,"line":1185},43,[57,1187,1188],{"class":67},"        },\n",[57,1190,1192],{"class":59,"line":1191},44,[57,1193,915],{"class":67},[57,1195,1197],{"class":59,"line":1196},45,[57,1198,920],{"class":63},[57,1200,1202],{"class":59,"line":1201},46,[57,1203,95],{"emptyLinePlaceholder":94},[57,1205,1207,1209,1212],{"class":59,"line":1206},47,[57,1208,828],{"class":63},[57,1210,1211],{"class":74}," 'email.complained'",[57,1213,834],{"class":67},[57,1215,1217,1219,1221,1223,1226,1228,1230,1232,1234,1236,1238],{"class":59,"line":1216},48,[57,1218,839],{"class":67},[57,1220,842],{"class":111},[57,1222,139],{"class":67},[57,1224,1225],{"class":74},"`Complaint from ${",[57,1227,809],{"class":67},[57,1229,481],{"class":74},[57,1231,854],{"class":67},[57,1233,481],{"class":74},[57,1235,859],{"class":67},[57,1237,862],{"class":74},[57,1239,614],{"class":67},[57,1241,1243,1245,1247,1249],{"class":59,"line":1242},49,[57,1244,879],{"class":63},[57,1246,882],{"class":67},[57,1248,596],{"class":111},[57,1250,887],{"class":67},[57,1252,1254,1256,1258,1260],{"class":59,"line":1253},50,[57,1255,892],{"class":67},[57,1257,223],{"class":63},[57,1259,530],{"class":104},[57,1261,899],{"class":67},[57,1263,1265,1267,1270,1273,1275],{"class":59,"line":1264},51,[57,1266,988],{"class":67},[57,1268,1269],{"class":74},"'complained'",[57,1271,1272],{"class":67},", suppressEmail: ",[57,1274,452],{"class":104},[57,1276,899],{"class":67},[57,1278,1280],{"class":59,"line":1279},52,[57,1281,915],{"class":67},[57,1283,1285],{"class":59,"line":1284},53,[57,1286,920],{"class":63},[57,1288,1290],{"class":59,"line":1289},54,[57,1291,95],{"emptyLinePlaceholder":94},[57,1293,1295,1298],{"class":59,"line":1294},55,[57,1296,1297],{"class":63},"    default",[57,1299,834],{"class":67},[57,1301,1303,1305,1307,1309,1312,1314,1316,1318,1320],{"class":59,"line":1302},56,[57,1304,839],{"class":67},[57,1306,842],{"class":111},[57,1308,139],{"class":67},[57,1310,1311],{"class":74},"`Unhandled event: ${",[57,1313,809],{"class":67},[57,1315,481],{"class":74},[57,1317,809],{"class":67},[57,1319,862],{"class":74},[57,1321,614],{"class":67},[57,1323,1325],{"class":59,"line":1324},57,[57,1326,321],{"class":67},[57,1328,1330],{"class":59,"line":1329},58,[57,1331,711],{"class":67},[713,1333,1336],{"title":1334,"type":1335},"Über die E-Mail-Adresse korrelieren, nicht über eine Message-ID","info",[11,1337,1338,1339,1341,1342,1344,1345,1347,1348,1350,1351,1354],{},"Ausgehende Payloads enthalten keine Message-ID. Die ",[32,1340,854],{},"-Map ist ein flacher Satz primitiver Werte, adressiert über die Empfängeradresse ",[32,1343,859],{}," (plus ",[32,1346,1126],{}," bei Klicks), und die ",[32,1349,854],{}," jedes Events trägt ihren eigenen ISO-8601-",[32,1352,1353],{},"timestamp",". Ordnen Sie Events Ihren eigenen Datensätzen über E-Mail-Adresse und Zeitstempel zu.",[15,1356,1358],{"id":1357},"bounce-behandlung","Bounce-Behandlung",[11,1360,1361],{},"Bounces sind das wichtigste zu verarbeitende Event. Hard Bounces bedeuten, dass die Adresse dauerhaft ungültig ist — weiterhin dorthin zu senden schadet Ihrer Absenderreputation:",[48,1363,1365],{"className":50,"code":1364,"language":52,"meta":53,"style":53},"case 'email.bounced':\n  const { email, bounceType, message } = event.data as {\n    email: string\n    bounceType: 'hard' | 'soft'\n    message: string \u002F\u002F provider-supplied reason, '' when none given\n    timestamp: string\n  }\n\n  if (bounceType === 'hard') {\n    \u002F\u002F Permanently suppress this address\n    await db.users.update({\n      where: { email },\n      data: { emailStatus: 'hard_bounced', suppressEmail: true, bounceReason: message },\n    })\n  } else {\n    \u002F\u002F Soft bounce — log it, but don't suppress yet\n    await db.bounceLog.create({\n      data: { email, type: 'soft', reason: message, timestamp: event.data.timestamp },\n    })\n  }\n  break\n",[32,1366,1367,1376,1408,1417,1433,1445,1454,1458,1462,1476,1481,1492,1497,1512,1517,1527,1532,1543,1554,1558,1562],{"__ignoreMap":53},[57,1368,1369,1372,1374],{"class":59,"line":60},[57,1370,1371],{"class":63},"case",[57,1373,931],{"class":74},[57,1375,834],{"class":67},[57,1377,1378,1380,1383,1385,1387,1390,1392,1395,1398,1401,1404,1406],{"class":59,"line":78},[57,1379,188],{"class":63},[57,1381,1382],{"class":67}," { ",[57,1384,859],{"class":104},[57,1386,170],{"class":67},[57,1388,1389],{"class":104},"bounceType",[57,1391,170],{"class":67},[57,1393,1394],{"class":104},"message",[57,1396,1397],{"class":67}," } ",[57,1399,1400],{"class":63},"=",[57,1402,1403],{"class":67}," event.data ",[57,1405,223],{"class":63},[57,1407,182],{"class":67},[57,1409,1410,1413,1415],{"class":59,"line":91},[57,1411,1412],{"class":166},"    email",[57,1414,527],{"class":63},[57,1416,226],{"class":104},[57,1418,1419,1422,1424,1427,1430],{"class":59,"line":98},[57,1420,1421],{"class":166},"    bounceType",[57,1423,527],{"class":63},[57,1425,1426],{"class":74}," 'hard'",[57,1428,1429],{"class":63}," |",[57,1431,1432],{"class":74}," 'soft'\n",[57,1434,1435,1438,1440,1442],{"class":59,"line":118},[57,1436,1437],{"class":166},"    message",[57,1439,527],{"class":63},[57,1441,530],{"class":104},[57,1443,1444],{"class":126}," \u002F\u002F provider-supplied reason, '' when none given\n",[57,1446,1447,1450,1452],{"class":59,"line":123},[57,1448,1449],{"class":166},"    timestamp",[57,1451,527],{"class":63},[57,1453,226],{"class":104},[57,1455,1456],{"class":59,"line":130},[57,1457,321],{"class":67},[57,1459,1460],{"class":59,"line":185},[57,1461,95],{"emptyLinePlaceholder":94},[57,1463,1464,1466,1469,1472,1474],{"class":59,"line":204},[57,1465,264],{"class":63},[57,1467,1468],{"class":67}," (bounceType ",[57,1470,1471],{"class":63},"===",[57,1473,1426],{"class":74},[57,1475,389],{"class":67},[57,1477,1478],{"class":59,"line":229},[57,1479,1480],{"class":126},"    \u002F\u002F Permanently suppress this address\n",[57,1482,1483,1486,1488,1490],{"class":59,"line":250},[57,1484,1485],{"class":63},"    await",[57,1487,882],{"class":67},[57,1489,596],{"class":111},[57,1491,887],{"class":67},[57,1493,1494],{"class":59,"line":255},[57,1495,1496],{"class":67},"      where: { email },\n",[57,1498,1499,1502,1505,1507,1509],{"class":59,"line":261},[57,1500,1501],{"class":67},"      data: { emailStatus: ",[57,1503,1504],{"class":74},"'hard_bounced'",[57,1506,1272],{"class":67},[57,1508,452],{"class":104},[57,1510,1511],{"class":67},", bounceReason: message },\n",[57,1513,1514],{"class":59,"line":286},[57,1515,1516],{"class":67},"    })\n",[57,1518,1519,1522,1525],{"class":59,"line":318},[57,1520,1521],{"class":67},"  } ",[57,1523,1524],{"class":63},"else",[57,1526,182],{"class":67},[57,1528,1529],{"class":59,"line":324},[57,1530,1531],{"class":126},"    \u002F\u002F Soft bounce — log it, but don't suppress yet\n",[57,1533,1534,1536,1539,1541],{"class":59,"line":329},[57,1535,1485],{"class":63},[57,1537,1538],{"class":67}," db.bounceLog.",[57,1540,1140],{"class":111},[57,1542,887],{"class":67},[57,1544,1545,1548,1551],{"class":59,"line":335},[57,1546,1547],{"class":67},"      data: { email, type: ",[57,1549,1550],{"class":74},"'soft'",[57,1552,1553],{"class":67},", reason: message, timestamp: event.data.timestamp },\n",[57,1555,1556],{"class":59,"line":375},[57,1557,1516],{"class":67},[57,1559,1560],{"class":59,"line":392},[57,1561,321],{"class":67},[57,1563,1564],{"class":59,"line":416},[57,1565,1566],{"class":63},"  break\n",[713,1568,1570],{"title":1569,"type":1335},"Automatische Unterdrückung",[11,1571,1572],{},"Owlat nimmt hart gebouncte Adressen automatisch in Ihre Blockliste auf. Der Webhook erlaubt es Ihnen, diesen Zustand in Ihrer eigenen Datenbank zu spiegeln, damit Sie Sendungen an diese Adressen gar nicht erst auslösen.",[15,1574,1576],{"id":1575},"events-die-owlat-auslöst","Events, die Owlat auslöst",[11,1578,1579],{},"Owlat sendet diese acht ausgehenden Events. Abonnieren Sie pro Webhook diejenigen, die für Sie relevant sind:",[1581,1582,1583,1598],"table",{},[1584,1585,1586],"thead",{},[1587,1588,1589,1593],"tr",{},[1590,1591,1592],"th",{},"Event",[1590,1594,1595,1597],{},[32,1596,854],{},"-Felder",[1599,1600,1601,1628,1641,1654,1669,1692,1705,1724],"tbody",{},[1587,1602,1603,1609],{},[1604,1605,1606],"td",{},[32,1607,1608],{},"email.sent",[1604,1610,1611,170,1613,1616,1617,1620,1621,1616,1624,1620,1626],{},[32,1612,859],{},[32,1614,1615],{},"campaignId"," (oder ",[32,1618,1619],{},"null","), ",[32,1622,1623],{},"transactionalEmailId",[32,1625,1619],{},[32,1627,1353],{},[1587,1629,1630,1635],{},[1604,1631,1632],{},[32,1633,1634],{},"email.delivered",[1604,1636,1637,170,1639],{},[32,1638,859],{},[32,1640,1353],{},[1587,1642,1643,1648],{},[1604,1644,1645],{},[32,1646,1647],{},"email.opened",[1604,1649,1650,170,1652],{},[32,1651,859],{},[32,1653,1353],{},[1587,1655,1656,1661],{},[1604,1657,1658],{},[32,1659,1660],{},"email.clicked",[1604,1662,1663,170,1665,170,1667],{},[32,1664,859],{},[32,1666,1126],{},[32,1668,1353],{},[1587,1670,1671,1676],{},[1604,1672,1673],{},[32,1674,1675],{},"email.bounced",[1604,1677,1678,170,1680,163,1682,360,1685,1620,1688,170,1690],{},[32,1679,859],{},[32,1681,1389],{},[32,1683,1684],{},"hard",[32,1686,1687],{},"soft",[32,1689,1394],{},[32,1691,1353],{},[1587,1693,1694,1699],{},[1604,1695,1696],{},[32,1697,1698],{},"email.complained",[1604,1700,1701,170,1703],{},[32,1702,859],{},[32,1704,1353],{},[1587,1706,1707,1712],{},[1604,1708,1709],{},[32,1710,1711],{},"contact.created",[1604,1713,1714,170,1717,170,1719,170,1722],{},[32,1715,1716],{},"contactId",[32,1718,859],{},[32,1720,1721],{},"source",[32,1723,1353],{},[1587,1725,1726,1731],{},[1604,1727,1728],{},[32,1729,1730],{},"topic.unsubscribed",[1604,1732,1733,170,1735,170,1737,170,1740],{},[32,1734,1716],{},[32,1736,859],{},[32,1738,1739],{},"unsubscribedAt",[32,1741,1742],{},"listsRemoved",[713,1744,1747],{"title":1745,"type":1746},"topic.unsubscribed.listsRemoved ist ein JSON-String","warning",[11,1748,1749,1750,1752,1753,1755,1756,1759,1760,1763,1764,1767,1768,1770,1771,1773],{},"Die ",[32,1751,854],{},"-Map ist ein flacher Satz primitiver Werte, daher wird ",[32,1754,1742],{}," als JSON-kodierter ",[27,1757,1758],{},"String"," ausgeliefert, nicht als Array. Empfänger müssen ",[32,1761,1762],{},"JSON.parse(event.data.listsRemoved)"," aufrufen, um die Liste ",[32,1765,1766],{},"{ topicId, topicName }[]"," zu erhalten. ",[32,1769,1739],{}," sind Epoch-Millisekunden (eine Zahl), anders als der ISO-8601-",[32,1772,1353],{}," der E-Mail-Events.",[15,1775,1777],{"id":1776},"aufbau-des-webhook-payloads","Aufbau des Webhook-Payloads",[11,1779,1780,1781,1783,1784,1786,1787,1789],{},"Jede Webhook-Zustellung folgt diesem Umschlag. Beachten Sie, dass ",[32,1782,854],{}," zusätzlich zum ",[32,1785,1353],{}," des Umschlags einen eigenen ISO-8601-",[32,1788,1353],{}," trägt:",[48,1791,1794],{"className":1792,"code":1793,"language":306,"meta":53,"style":53},"language-json shiki shiki-themes github-light github-dark-dimmed","{\n  \"event\": \"email.delivered\",\n  \"timestamp\": \"2026-03-19T12:00:00.000Z\",\n  \"data\": {\n    \"email\": \"mira@acme.io\",\n    \"timestamp\": \"2026-03-19T12:00:00.000Z\"\n  }\n}\n",[32,1795,1796,1801,1815,1827,1835,1847,1857,1861],{"__ignoreMap":53},[57,1797,1798],{"class":59,"line":60},[57,1799,1800],{"class":67},"{\n",[57,1802,1803,1807,1810,1813],{"class":59,"line":78},[57,1804,1806],{"class":1805},"snmFh","  \"event\"",[57,1808,1809],{"class":67},": ",[57,1811,1812],{"class":74},"\"email.delivered\"",[57,1814,1159],{"class":67},[57,1816,1817,1820,1822,1825],{"class":59,"line":91},[57,1818,1819],{"class":1805},"  \"timestamp\"",[57,1821,1809],{"class":67},[57,1823,1824],{"class":74},"\"2026-03-19T12:00:00.000Z\"",[57,1826,1159],{"class":67},[57,1828,1829,1832],{"class":59,"line":98},[57,1830,1831],{"class":1805},"  \"data\"",[57,1833,1834],{"class":67},": {\n",[57,1836,1837,1840,1842,1845],{"class":59,"line":118},[57,1838,1839],{"class":1805},"    \"email\"",[57,1841,1809],{"class":67},[57,1843,1844],{"class":74},"\"mira@acme.io\"",[57,1846,1159],{"class":67},[57,1848,1849,1852,1854],{"class":59,"line":123},[57,1850,1851],{"class":1805},"    \"timestamp\"",[57,1853,1809],{"class":67},[57,1855,1856],{"class":74},"\"2026-03-19T12:00:00.000Z\"\n",[57,1858,1859],{"class":59,"line":130},[57,1860,321],{"class":67},[57,1862,1863],{"class":59,"line":185},[57,1864,711],{"class":67},[11,1866,1867],{},"Bei jeder Zustellung enthaltene Header:",[1581,1869,1870,1880],{},[1584,1871,1872],{},[1587,1873,1874,1877],{},[1590,1875,1876],{},"Header",[1590,1878,1879],{},"Beschreibung",[1599,1881,1882,1892,1902,1912,1924],{},[1587,1883,1884,1889],{},[1604,1885,1886],{},[32,1887,1888],{},"X-Signature",[1604,1890,1891],{},"HMAC-SHA256-Hex-Digest des Payload-Bodys",[1587,1893,1894,1899],{},[1604,1895,1896],{},[32,1897,1898],{},"X-Timestamp",[1604,1900,1901],{},"Unix-Zeitstempel (Sekunden) des Absendezeitpunkts der Anfrage",[1587,1903,1904,1909],{},[1604,1905,1906],{},[32,1907,1908],{},"X-Webhook-Id",[1604,1910,1911],{},"Die ID der Webhook-Konfiguration",[1587,1913,1914,1919],{},[1604,1915,1916],{},[32,1917,1918],{},"Content-Type",[1604,1920,1921],{},[32,1922,1923],{},"application\u002Fjson",[1587,1925,1926,1931],{},[1604,1927,1928],{},[32,1929,1930],{},"User-Agent",[1604,1932,1933],{},[32,1934,1935],{},"Owlat-Webhooks\u002F1.0",[15,1937,1939],{"id":1938},"nächste-schritte","Nächste Schritte",[20,1941,1942,1950,1957,1964],{},[23,1943,1944,1949],{},[1945,1946,1948],"a",{"href":1947},"\u002Fapi\u002Fwebhook-payloads","Webhook-Payloads"," — der maßgebliche Payload-Contract je Event",[23,1951,1952,1956],{},[1945,1953,1955],{"href":1954},"\u002Fapi\u002Fwebhooks","Referenz zur Webhooks-API"," — Konfiguration, Signierung und Retry-Verhalten",[23,1958,1959,1963],{},[1945,1960,1962],{"href":1961},"\u002Fguide\u002Fdeliverability","Leitfaden zur Zustellbarkeit"," — Inbox-Platzierung und Reputation überwachen",[23,1965,1966,1970],{},[1945,1967,1969],{"href":1968},"\u002Fexamples\u002Fbilling-email","Rechnungs-E-Mail"," — die Zustellung von Zahlungsbestätigungen verfolgen",[1972,1973,1974],"style",{},"html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html pre.shiki code .stnAF, html code.shiki .stnAF{--shiki-default:#E36209;--shiki-dark:#F69D50}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);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .snmFh, html code.shiki .snmFh{--shiki-default:#005CC5;--shiki-dark:#8DDB8C}",{"title":53,"searchDepth":78,"depth":78,"links":1976},[1977,1978,1979,1980,1981,1982,1983],{"id":17,"depth":78,"text":18},{"id":45,"depth":78,"text":46},{"id":725,"depth":78,"text":726},{"id":1357,"depth":78,"text":1358},{"id":1575,"depth":78,"text":1576},{"id":1776,"depth":78,"text":1777},{"id":1938,"depth":78,"text":1939},"md",{},"\u002Fexamples\u002Fwebhook-handler",{"title":6,"description":13},"4.examples\u002F5.webhook-handler","d1au639JLXrDiHapfUpMJ9lcqNI0Z_TTI7brrrYlI68",[1991,1995],{"title":1992,"path":1993,"stem":1994,"children":-1},"Kontaktsynchronisierung","\u002Fexamples\u002Fcontact-sync","4.examples\u002F4.contact-sync",{"title":1996,"path":1997,"stem":1998,"children":-1},"Mehrsprachige E-Mails","\u002Fexamples\u002Fmultilingual-email","4.examples\u002F6.multilingual-email",1786915111805]