[{"data":1,"prerenderedAt":2441},["ShallowReactive",2],{"docs:/docs/concepts/webhooks":3},{"id":4,"title":5,"body":6,"description":2434,"extension":2435,"meta":2436,"navigation":407,"path":2437,"seo":2438,"stem":2439,"__hash__":2440},"docs/docs/concepts/webhooks.md","Webhooks",{"type":7,"value":8,"toc":2414},"minimark",[9,13,17,51,56,61,64,69,192,202,206,220,327,330,565,574,578,581,584,705,708,711,715,721,753,757,761,764,779,783,799,1147,1150,1154,1157,1178,1189,1193,1196,1217,1221,1228,1232,1235,1276,1280,1283,1382,1385,1389,1424,1428,1434,2381,2385,2410],[10,11,5],"h1",{"id":12},"webhooks",[14,15,16],"p",{},"Webhooks allow your external systems to react to NoLag events. Configure webhooks to pre-populate state when actors subscribe, and trigger external workflows when messages are published. Webhook payloads include the actor's access scope for automatic multi-tenant isolation.",[18,19,21,27],"callout",{"type":20},"info",[14,22,23],{},[24,25,26],"strong",{},"Key Concept",[14,28,29,30,33,34,38,39,42,43,46,47,50],{},"Webhooks are configured on the ",[24,31,32],{},"app",". ",[35,36,37],"code",{},"hydrationWebhook"," and ",[35,40,41],{},"triggerWebhook"," set app-wide defaults, and ",[35,44,45],{},"topicConfigs.\u003Ctopic>.webhooks.onSubscribe"," / ",[35,48,49],{},"onPublish"," override them for one topic. When both exist for a topic, the per-topic one is used. Different topics can therefore call different endpoints, giving you fine-grained control over integrations.",[52,53,55],"h2",{"id":54},"webhook-types","Webhook Types",[57,58,60],"h3",{"id":59},"hydration-webhook","Hydration Webhook",[14,62,63],{},"Called when an actor subscribes to a topic. Use this to pre-populate the actor's state with current data (e.g., recent messages, current game state, latest prices).",[65,66,68],"h4",{"id":67},"request-format","Request Format",[70,71,72],"code-tabs",{},[73,74,80],"pre",{"className":75,"code":76,"filename":77,"language":78,"meta":79,"style":79},"language-json shiki shiki-themes github-light github-dark","{\n  \"actorId\": \"01939f83-8b57-7c3e-a456-426614174000\",\n  \"roomName\": \"general-chat\",\n  \"topicName\": \"messages\",\n  \"scope\": {\n    \"accessScopeId\": \"01939f83-8b57-...\",\n    \"slug\": \"client-acme\",\n    \"name\": \"Acme Corp\"\n  }\n}\n","JSON","json","",[35,81,82,91,108,121,134,143,156,169,180,186],{"__ignoreMap":79},[83,84,87],"span",{"class":85,"line":86},"line",1,[83,88,90],{"class":89},"sVt8B","{\n",[83,92,94,98,101,105],{"class":85,"line":93},2,[83,95,97],{"class":96},"sj4cs","  \"actorId\"",[83,99,100],{"class":89},": ",[83,102,104],{"class":103},"sZZnC","\"01939f83-8b57-7c3e-a456-426614174000\"",[83,106,107],{"class":89},",\n",[83,109,111,114,116,119],{"class":85,"line":110},3,[83,112,113],{"class":96},"  \"roomName\"",[83,115,100],{"class":89},[83,117,118],{"class":103},"\"general-chat\"",[83,120,107],{"class":89},[83,122,124,127,129,132],{"class":85,"line":123},4,[83,125,126],{"class":96},"  \"topicName\"",[83,128,100],{"class":89},[83,130,131],{"class":103},"\"messages\"",[83,133,107],{"class":89},[83,135,137,140],{"class":85,"line":136},5,[83,138,139],{"class":96},"  \"scope\"",[83,141,142],{"class":89},": {\n",[83,144,146,149,151,154],{"class":85,"line":145},6,[83,147,148],{"class":96},"    \"accessScopeId\"",[83,150,100],{"class":89},[83,152,153],{"class":103},"\"01939f83-8b57-...\"",[83,155,107],{"class":89},[83,157,159,162,164,167],{"class":85,"line":158},7,[83,160,161],{"class":96},"    \"slug\"",[83,163,100],{"class":89},[83,165,166],{"class":103},"\"client-acme\"",[83,168,107],{"class":89},[83,170,172,175,177],{"class":85,"line":171},8,[83,173,174],{"class":96},"    \"name\"",[83,176,100],{"class":89},[83,178,179],{"class":103},"\"Acme Corp\"\n",[83,181,183],{"class":85,"line":182},9,[83,184,185],{"class":89},"  }\n",[83,187,189],{"class":85,"line":188},10,[83,190,191],{"class":89},"}\n",[14,193,194,197,198,201],{},[35,195,196],{},"scope"," is ",[35,199,200],{},"null"," for unscoped actors, or an object with the actor's access scope details for multi-tenant routing.",[65,203,205],{"id":204},"response","Response",[14,207,208,209,212,213,215,216,219],{},"Return any JSON body. The broker forwards it once per subscribe to the subscribing actor as a ",[35,210,211],{},"hydration"," frame, which the SDKs surface as the ",[35,214,211],{}," event with the bare topic name and the body as ",[35,217,218],{},"data",". It is delivered as its own event, not through the topic's message handlers, so your code can tell \"state on join\" from live traffic.",[70,221,222],{},[73,223,225],{"className":75,"code":224,"filename":77,"language":78,"meta":79,"style":79},"{\n  \"recentMessages\": [\n    { \"from\": \"alice\", \"text\": \"Hello!\", \"timestamp\": 1234567890 },\n    { \"from\": \"bob\", \"text\": \"Hi there!\", \"timestamp\": 1234567891 }\n  ],\n  \"participantCount\": 5\n}\n",[35,226,227,231,239,276,308,313,323],{"__ignoreMap":79},[83,228,229],{"class":85,"line":86},[83,230,90],{"class":89},[83,232,233,236],{"class":85,"line":93},[83,234,235],{"class":96},"  \"recentMessages\"",[83,237,238],{"class":89},": [\n",[83,240,241,244,247,249,252,255,258,260,263,265,268,270,273],{"class":85,"line":110},[83,242,243],{"class":89},"    { ",[83,245,246],{"class":96},"\"from\"",[83,248,100],{"class":89},[83,250,251],{"class":103},"\"alice\"",[83,253,254],{"class":89},", ",[83,256,257],{"class":96},"\"text\"",[83,259,100],{"class":89},[83,261,262],{"class":103},"\"Hello!\"",[83,264,254],{"class":89},[83,266,267],{"class":96},"\"timestamp\"",[83,269,100],{"class":89},[83,271,272],{"class":96},"1234567890",[83,274,275],{"class":89}," },\n",[83,277,278,280,282,284,287,289,291,293,296,298,300,302,305],{"class":85,"line":123},[83,279,243],{"class":89},[83,281,246],{"class":96},[83,283,100],{"class":89},[83,285,286],{"class":103},"\"bob\"",[83,288,254],{"class":89},[83,290,257],{"class":96},[83,292,100],{"class":89},[83,294,295],{"class":103},"\"Hi there!\"",[83,297,254],{"class":89},[83,299,267],{"class":96},[83,301,100],{"class":89},[83,303,304],{"class":96},"1234567891",[83,306,307],{"class":89}," }\n",[83,309,310],{"class":85,"line":136},[83,311,312],{"class":89},"  ],\n",[83,314,315,318,320],{"class":85,"line":145},[83,316,317],{"class":96},"  \"participantCount\"",[83,319,100],{"class":89},[83,321,322],{"class":96},"5\n",[83,324,325],{"class":85,"line":158},[83,326,191],{"class":89},[14,328,329],{},"Receive it in the SDK:",[70,331,332,464,510],{},[73,333,338],{"className":334,"code":335,"filename":336,"language":337,"meta":79,"style":79},"language-typescript shiki shiki-themes github-light github-dark","client.on('hydration', ({ topic, data }) => {\n  // topic is the bare topic name, e.g. 'messages'\n  console.log('hydrated', topic, data)\n})\n\nconst room = client.setApp(APP_SLUG).setRoom('general-chat')\nroom.subscribe('messages') // the hydration webhook fires for this subscribe\n","TypeScript","typescript",[35,339,340,376,382,398,403,409,445],{"__ignoreMap":79},[83,341,342,345,349,352,355,358,362,364,366,369,373],{"class":85,"line":86},[83,343,344],{"class":89},"client.",[83,346,348],{"class":347},"sScJk","on",[83,350,351],{"class":89},"(",[83,353,354],{"class":103},"'hydration'",[83,356,357],{"class":89},", ({ ",[83,359,361],{"class":360},"s4XuR","topic",[83,363,254],{"class":89},[83,365,218],{"class":360},[83,367,368],{"class":89}," }) ",[83,370,372],{"class":371},"szBVR","=>",[83,374,375],{"class":89}," {\n",[83,377,378],{"class":85,"line":93},[83,379,381],{"class":380},"sJ8bj","  // topic is the bare topic name, e.g. 'messages'\n",[83,383,384,387,390,392,395],{"class":85,"line":110},[83,385,386],{"class":89},"  console.",[83,388,389],{"class":347},"log",[83,391,351],{"class":89},[83,393,394],{"class":103},"'hydrated'",[83,396,397],{"class":89},", topic, data)\n",[83,399,400],{"class":85,"line":123},[83,401,402],{"class":89},"})\n",[83,404,405],{"class":85,"line":136},[83,406,408],{"emptyLinePlaceholder":407},true,"\n",[83,410,411,414,417,420,423,426,428,431,434,437,439,442],{"class":85,"line":145},[83,412,413],{"class":371},"const",[83,415,416],{"class":96}," room",[83,418,419],{"class":371}," =",[83,421,422],{"class":89}," client.",[83,424,425],{"class":347},"setApp",[83,427,351],{"class":89},[83,429,430],{"class":96},"APP_SLUG",[83,432,433],{"class":89},").",[83,435,436],{"class":347},"setRoom",[83,438,351],{"class":89},[83,440,441],{"class":103},"'general-chat'",[83,443,444],{"class":89},")\n",[83,446,447,450,453,455,458,461],{"class":85,"line":158},[83,448,449],{"class":89},"room.",[83,451,452],{"class":347},"subscribe",[83,454,351],{"class":89},[83,456,457],{"class":103},"'messages'",[83,459,460],{"class":89},") ",[83,462,463],{"class":380},"// the hydration webhook fires for this subscribe\n",[73,465,470],{"className":466,"code":467,"filename":468,"language":469,"meta":79,"style":79},"language-python shiki shiki-themes github-light github-dark","def on_hydration(topic, data):\n    # topic is the bare topic name, e.g. 'messages'\n    print('hydrated', topic, data)\n\nclient.on('hydration', on_hydration)\n\nroom = client.set_app(APP_SLUG).set_room('general-chat')\nawait room.subscribe('messages')  # the hydration webhook fires for this subscribe\n","Python","python",[35,471,472,477,482,487,491,496,500,505],{"__ignoreMap":79},[83,473,474],{"class":85,"line":86},[83,475,476],{},"def on_hydration(topic, data):\n",[83,478,479],{"class":85,"line":93},[83,480,481],{},"    # topic is the bare topic name, e.g. 'messages'\n",[83,483,484],{"class":85,"line":110},[83,485,486],{},"    print('hydrated', topic, data)\n",[83,488,489],{"class":85,"line":123},[83,490,408],{"emptyLinePlaceholder":407},[83,492,493],{"class":85,"line":136},[83,494,495],{},"client.on('hydration', on_hydration)\n",[83,497,498],{"class":85,"line":145},[83,499,408],{"emptyLinePlaceholder":407},[83,501,502],{"class":85,"line":158},[83,503,504],{},"room = client.set_app(APP_SLUG).set_room('general-chat')\n",[83,506,507],{"class":85,"line":171},[83,508,509],{},"await room.subscribe('messages')  # the hydration webhook fires for this subscribe\n",[73,511,516],{"className":512,"code":513,"filename":514,"language":515,"meta":79,"style":79},"language-go shiki shiki-themes github-light github-dark","client.On(\"hydration\", func(args ...any) {\n    topic := args[0].(string) // bare topic name, e.g. \"messages\"\n    data := args[1]\n    fmt.Println(\"hydrated\", topic, data)\n})\n\nroom := client.SetApp(appSlug).SetRoom(\"general-chat\")\nroom.Subscribe(\"messages\", func(data any, meta nolag.MessageMeta) {\n    fmt.Println(\"live:\", data)\n})\n","Go","go",[35,517,518,523,528,533,538,542,546,551,556,561],{"__ignoreMap":79},[83,519,520],{"class":85,"line":86},[83,521,522],{},"client.On(\"hydration\", func(args ...any) {\n",[83,524,525],{"class":85,"line":93},[83,526,527],{},"    topic := args[0].(string) // bare topic name, e.g. \"messages\"\n",[83,529,530],{"class":85,"line":110},[83,531,532],{},"    data := args[1]\n",[83,534,535],{"class":85,"line":123},[83,536,537],{},"    fmt.Println(\"hydrated\", topic, data)\n",[83,539,540],{"class":85,"line":136},[83,541,402],{},[83,543,544],{"class":85,"line":145},[83,545,408],{"emptyLinePlaceholder":407},[83,547,548],{"class":85,"line":158},[83,549,550],{},"room := client.SetApp(appSlug).SetRoom(\"general-chat\")\n",[83,552,553],{"class":85,"line":171},[83,554,555],{},"room.Subscribe(\"messages\", func(data any, meta nolag.MessageMeta) {\n",[83,557,558],{"class":85,"line":182},[83,559,560],{},"    fmt.Println(\"live:\", data)\n",[83,562,563],{"class":85,"line":188},[83,564,402],{},[14,566,567,568,570,571,573],{},"The hydration call is asynchronous and best-effort: the subscribe succeeds immediately, and the ",[35,569,211],{}," event arrives when your endpoint answers. A non-JSON body, a 4xx, or a failure after the retries described below is logged and dropped; the subscriber simply receives no ",[35,572,211],{}," event, and nothing is written to the dead letter queue.",[57,575,577],{"id":576},"trigger-webhook","Trigger Webhook",[14,579,580],{},"Called when an actor publishes data to a topic. Use this to trigger external workflows, store messages, send notifications, or integrate with third-party services.",[65,582,68],{"id":583},"request-format-1",[70,585,586],{},[73,587,589],{"className":75,"code":588,"filename":77,"language":78,"meta":79,"style":79},"{\n  \"roomName\": \"general-chat\",\n  \"topicName\": \"messages\",\n  \"actorId\": \"01939f83-8b57-7c3e-a456-426614174000\",\n  \"data\": {\n    \"text\": \"Hello everyone!\",\n    \"timestamp\": 1234567890\n  },\n  \"scope\": {\n    \"accessScopeId\": \"01939f83-8b57-...\",\n    \"slug\": \"client-acme\",\n    \"name\": \"Acme Corp\"\n  }\n}\n",[35,590,591,595,605,615,625,632,644,654,659,665,675,686,695,700],{"__ignoreMap":79},[83,592,593],{"class":85,"line":86},[83,594,90],{"class":89},[83,596,597,599,601,603],{"class":85,"line":93},[83,598,113],{"class":96},[83,600,100],{"class":89},[83,602,118],{"class":103},[83,604,107],{"class":89},[83,606,607,609,611,613],{"class":85,"line":110},[83,608,126],{"class":96},[83,610,100],{"class":89},[83,612,131],{"class":103},[83,614,107],{"class":89},[83,616,617,619,621,623],{"class":85,"line":123},[83,618,97],{"class":96},[83,620,100],{"class":89},[83,622,104],{"class":103},[83,624,107],{"class":89},[83,626,627,630],{"class":85,"line":136},[83,628,629],{"class":96},"  \"data\"",[83,631,142],{"class":89},[83,633,634,637,639,642],{"class":85,"line":145},[83,635,636],{"class":96},"    \"text\"",[83,638,100],{"class":89},[83,640,641],{"class":103},"\"Hello everyone!\"",[83,643,107],{"class":89},[83,645,646,649,651],{"class":85,"line":158},[83,647,648],{"class":96},"    \"timestamp\"",[83,650,100],{"class":89},[83,652,653],{"class":96},"1234567890\n",[83,655,656],{"class":85,"line":171},[83,657,658],{"class":89},"  },\n",[83,660,661,663],{"class":85,"line":182},[83,662,139],{"class":96},[83,664,142],{"class":89},[83,666,667,669,671,673],{"class":85,"line":188},[83,668,148],{"class":96},[83,670,100],{"class":89},[83,672,153],{"class":103},[83,674,107],{"class":89},[83,676,678,680,682,684],{"class":85,"line":677},11,[83,679,161],{"class":96},[83,681,100],{"class":89},[83,683,166],{"class":103},[83,685,107],{"class":89},[83,687,689,691,693],{"class":85,"line":688},12,[83,690,174],{"class":96},[83,692,100],{"class":89},[83,694,179],{"class":103},[83,696,698],{"class":85,"line":697},13,[83,699,185],{"class":89},[83,701,703],{"class":85,"line":702},14,[83,704,191],{"class":89},[65,706,205],{"id":707},"response-1",[14,709,710],{},"Return any 2xx status code to acknowledge receipt. The response body is ignored. The call is asynchronous, so it never delays the publish or its delivery to subscribers.",[52,712,714],{"id":713},"scope-in-payloads","Scope in Payloads",[14,716,717,718,720],{},"All webhook payloads include a ",[35,719,196],{}," field containing the actor's access scope information. This allows your backend to route requests to the correct tenant without additional lookups.",[722,723,724,744],"ul",{},[725,726,727,730,731,733,734,254,737,740,741],"li",{},[24,728,729],{},"Scoped actors:"," ",[35,732,196],{}," contains ",[35,735,736],{},"accessScopeId",[35,738,739],{},"slug",", and ",[35,742,743],{},"name",[725,745,746,730,749,197,751],{},[24,747,748],{},"Unscoped actors:",[35,750,196],{},[35,752,200],{},[52,754,756],{"id":755},"configuration","Configuration",[57,758,760],{"id":759},"via-dashboard","Via Dashboard",[14,762,763],{},"The dashboard edits the per-topic overrides:",[765,766,767,770,773,776],"ol",{},[725,768,769],{},"Navigate to your App's \"Webhooks\" page",[725,771,772],{},"Click on a topic to expand its webhook settings",[725,774,775],{},"Configure the On Subscribe (hydration) and/or On Publish (trigger) webhook URLs and headers",[725,777,778],{},"Save changes for that topic",[57,780,782],{"id":781},"via-rest-api","Via REST API",[14,784,785,786,38,788,790,791,794,795,798],{},"Patch the app with a project-scoped API key. ",[35,787,37],{},[35,789,41],{}," are the app-wide defaults; ",[35,792,793],{},"topicConfigs"," holds the per-topic overrides. Each webhook is ",[35,796,797],{},"{ \"url\": string, \"headers\"?: object }",".",[70,800,801,943],{},[73,802,807],{"className":803,"code":804,"filename":805,"language":806,"meta":79,"style":79},"language-bash shiki shiki-themes github-light github-dark","curl -X PATCH https://api.nolag.app/v1/apps/{appId} \\\n  -H \"Authorization: Bearer nlg_live_xxx.secret\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"hydrationWebhook\": {\n          \"url\": \"https://api.example.com/hydrate\",\n          \"headers\": { \"Authorization\": \"Bearer xxx\" }\n        },\n        \"triggerWebhook\": {\n          \"url\": \"https://api.example.com/trigger\",\n          \"headers\": { \"Authorization\": \"Bearer xxx\" }\n        },\n        \"topicConfigs\": {\n          \"messages\": {\n            \"webhooks\": {\n              \"onSubscribe\": { \"url\": \"https://api.example.com/hydrate-messages\" },\n              \"onPublish\": { \"url\": \"https://api.example.com/trigger-messages\" }\n            }\n          }\n        }\n      }'\n","Terminal","bash",[35,808,809,826,836,845,853,858,863,868,873,878,883,887,891,896,901,907,913,919,925,931,937],{"__ignoreMap":79},[83,810,811,814,817,820,823],{"class":85,"line":86},[83,812,813],{"class":347},"curl",[83,815,816],{"class":96}," -X",[83,818,819],{"class":103}," PATCH",[83,821,822],{"class":103}," https://api.nolag.app/v1/apps/{appId}",[83,824,825],{"class":96}," \\\n",[83,827,828,831,834],{"class":85,"line":93},[83,829,830],{"class":96},"  -H",[83,832,833],{"class":103}," \"Authorization: Bearer nlg_live_xxx.secret\"",[83,835,825],{"class":96},[83,837,838,840,843],{"class":85,"line":110},[83,839,830],{"class":96},[83,841,842],{"class":103}," \"Content-Type: application/json\"",[83,844,825],{"class":96},[83,846,847,850],{"class":85,"line":123},[83,848,849],{"class":96},"  -d",[83,851,852],{"class":103}," '{\n",[83,854,855],{"class":85,"line":136},[83,856,857],{"class":103},"        \"hydrationWebhook\": {\n",[83,859,860],{"class":85,"line":145},[83,861,862],{"class":103},"          \"url\": \"https://api.example.com/hydrate\",\n",[83,864,865],{"class":85,"line":158},[83,866,867],{"class":103},"          \"headers\": { \"Authorization\": \"Bearer xxx\" }\n",[83,869,870],{"class":85,"line":171},[83,871,872],{"class":103},"        },\n",[83,874,875],{"class":85,"line":182},[83,876,877],{"class":103},"        \"triggerWebhook\": {\n",[83,879,880],{"class":85,"line":188},[83,881,882],{"class":103},"          \"url\": \"https://api.example.com/trigger\",\n",[83,884,885],{"class":85,"line":677},[83,886,867],{"class":103},[83,888,889],{"class":85,"line":688},[83,890,872],{"class":103},[83,892,893],{"class":85,"line":697},[83,894,895],{"class":103},"        \"topicConfigs\": {\n",[83,897,898],{"class":85,"line":702},[83,899,900],{"class":103},"          \"messages\": {\n",[83,902,904],{"class":85,"line":903},15,[83,905,906],{"class":103},"            \"webhooks\": {\n",[83,908,910],{"class":85,"line":909},16,[83,911,912],{"class":103},"              \"onSubscribe\": { \"url\": \"https://api.example.com/hydrate-messages\" },\n",[83,914,916],{"class":85,"line":915},17,[83,917,918],{"class":103},"              \"onPublish\": { \"url\": \"https://api.example.com/trigger-messages\" }\n",[83,920,922],{"class":85,"line":921},18,[83,923,924],{"class":103},"            }\n",[83,926,928],{"class":85,"line":927},19,[83,929,930],{"class":103},"          }\n",[83,932,934],{"class":85,"line":933},20,[83,935,936],{"class":103},"        }\n",[83,938,940],{"class":85,"line":939},21,[83,941,942],{"class":103},"      }'\n",[73,944,946],{"className":334,"code":945,"filename":336,"language":337,"meta":79,"style":79},"import { NoLagApi } from '@nolag/js-sdk'\n\nconst api = new NoLagApi(process.env.NOLAG_API_KEY) // nlg_live_...\n\n// The typed apps.update() covers name, slug, description and config;\n// send webhook fields through the generic request helper.\nawait api.request('PATCH', `/apps/${appId}`, {\n  hydrationWebhook: {\n    url: 'https://api.example.com/hydrate',\n    headers: { Authorization: 'Bearer xxx' }\n  },\n  triggerWebhook: {\n    url: 'https://api.example.com/trigger',\n    headers: { Authorization: 'Bearer xxx' }\n  },\n  topicConfigs: {\n    messages: {\n      webhooks: {\n        onSubscribe: { url: 'https://api.example.com/hydrate-messages' },\n        onPublish: { url: 'https://api.example.com/trigger-messages' }\n      }\n    }\n  }\n})\n",[35,947,948,962,966,992,996,1001,1006,1036,1041,1051,1061,1065,1070,1079,1087,1091,1096,1101,1106,1116,1126,1131,1137,1142],{"__ignoreMap":79},[83,949,950,953,956,959],{"class":85,"line":86},[83,951,952],{"class":371},"import",[83,954,955],{"class":89}," { NoLagApi } ",[83,957,958],{"class":371},"from",[83,960,961],{"class":103}," '@nolag/js-sdk'\n",[83,963,964],{"class":85,"line":93},[83,965,408],{"emptyLinePlaceholder":407},[83,967,968,970,973,975,978,981,984,987,989],{"class":85,"line":110},[83,969,413],{"class":371},[83,971,972],{"class":96}," api",[83,974,419],{"class":371},[83,976,977],{"class":371}," new",[83,979,980],{"class":347}," NoLagApi",[83,982,983],{"class":89},"(process.env.",[83,985,986],{"class":96},"NOLAG_API_KEY",[83,988,460],{"class":89},[83,990,991],{"class":380},"// nlg_live_...\n",[83,993,994],{"class":85,"line":123},[83,995,408],{"emptyLinePlaceholder":407},[83,997,998],{"class":85,"line":136},[83,999,1000],{"class":380},"// The typed apps.update() covers name, slug, description and config;\n",[83,1002,1003],{"class":85,"line":145},[83,1004,1005],{"class":380},"// send webhook fields through the generic request helper.\n",[83,1007,1008,1011,1014,1017,1019,1022,1024,1027,1030,1033],{"class":85,"line":158},[83,1009,1010],{"class":371},"await",[83,1012,1013],{"class":89}," api.",[83,1015,1016],{"class":347},"request",[83,1018,351],{"class":89},[83,1020,1021],{"class":103},"'PATCH'",[83,1023,254],{"class":89},[83,1025,1026],{"class":103},"`/apps/${",[83,1028,1029],{"class":89},"appId",[83,1031,1032],{"class":103},"}`",[83,1034,1035],{"class":89},", {\n",[83,1037,1038],{"class":85,"line":171},[83,1039,1040],{"class":89},"  hydrationWebhook: {\n",[83,1042,1043,1046,1049],{"class":85,"line":182},[83,1044,1045],{"class":89},"    url: ",[83,1047,1048],{"class":103},"'https://api.example.com/hydrate'",[83,1050,107],{"class":89},[83,1052,1053,1056,1059],{"class":85,"line":188},[83,1054,1055],{"class":89},"    headers: { Authorization: ",[83,1057,1058],{"class":103},"'Bearer xxx'",[83,1060,307],{"class":89},[83,1062,1063],{"class":85,"line":677},[83,1064,658],{"class":89},[83,1066,1067],{"class":85,"line":688},[83,1068,1069],{"class":89},"  triggerWebhook: {\n",[83,1071,1072,1074,1077],{"class":85,"line":697},[83,1073,1045],{"class":89},[83,1075,1076],{"class":103},"'https://api.example.com/trigger'",[83,1078,107],{"class":89},[83,1080,1081,1083,1085],{"class":85,"line":702},[83,1082,1055],{"class":89},[83,1084,1058],{"class":103},[83,1086,307],{"class":89},[83,1088,1089],{"class":85,"line":903},[83,1090,658],{"class":89},[83,1092,1093],{"class":85,"line":909},[83,1094,1095],{"class":89},"  topicConfigs: {\n",[83,1097,1098],{"class":85,"line":915},[83,1099,1100],{"class":89},"    messages: {\n",[83,1102,1103],{"class":85,"line":921},[83,1104,1105],{"class":89},"      webhooks: {\n",[83,1107,1108,1111,1114],{"class":85,"line":927},[83,1109,1110],{"class":89},"        onSubscribe: { url: ",[83,1112,1113],{"class":103},"'https://api.example.com/hydrate-messages'",[83,1115,275],{"class":89},[83,1117,1118,1121,1124],{"class":85,"line":933},[83,1119,1120],{"class":89},"        onPublish: { url: ",[83,1122,1123],{"class":103},"'https://api.example.com/trigger-messages'",[83,1125,307],{"class":89},[83,1127,1128],{"class":85,"line":939},[83,1129,1130],{"class":89},"      }\n",[83,1132,1134],{"class":85,"line":1133},22,[83,1135,1136],{"class":89},"    }\n",[83,1138,1140],{"class":85,"line":1139},23,[83,1141,185],{"class":89},[83,1143,1145],{"class":85,"line":1144},24,[83,1146,402],{"class":89},[14,1148,1149],{},"The broker picks up configuration changes for a connection at its next authentication or periodic revalidation (about every 10 minutes), so a newly configured webhook may take that long to apply to clients that are already connected.",[52,1151,1153],{"id":1152},"authentication","Authentication",[14,1155,1156],{},"Webhooks support two authentication methods:",[722,1158,1159,1168],{},[725,1160,1161,1164,1165],{},[24,1162,1163],{},"Query Parameters"," - Add auth tokens directly in the URL: ",[35,1166,1167],{},"https://api.example.com/webhook?api_key=xxx",[725,1169,1170,1173,1174,1177],{},[24,1171,1172],{},"Request Headers"," - Add custom headers like ",[35,1175,1176],{},"Authorization"," or API key headers",[18,1179,1181,1186],{"type":1180},"warning",[14,1182,1183],{},[24,1184,1185],{},"Security Note",[14,1187,1188],{},"Always use HTTPS for webhook URLs. Webhook headers are stored as part of the app's configuration in plain form, not encrypted at rest, and a failed trigger call copies them into its dead letter entry. Use a dedicated, revocable secret for webhook authentication rather than a broadly privileged credential.",[52,1190,1192],{"id":1191},"retry-behavior","Retry Behavior",[14,1194,1195],{},"Every webhook call, hydration or trigger, is made the same way:",[722,1197,1198,1205,1208,1211],{},[725,1199,1200,1201,1204],{},"Up to ",[24,1202,1203],{},"3 attempts"," in total: the initial call and two retries, waiting 1 second before the second attempt and 2 seconds before the third",[725,1206,1207],{},"Server errors (5xx) and connection errors trigger a retry",[725,1209,1210],{},"Client errors (4xx) do not; the call fails immediately",[725,1212,1213,1214],{},"Each attempt times out after ",[24,1215,1216],{},"30 seconds",[52,1218,1220],{"id":1219},"dead-letter-queue-dlq","Dead Letter Queue (DLQ)",[14,1222,1223,1224,1227],{},"When a ",[24,1225,1226],{},"trigger"," webhook fails after all attempts, the request is recorded in the Dead Letter Queue. Hydration failures are logged by the broker and dropped; they never reach the DLQ.",[57,1229,1231],{"id":1230},"dlq-entry-contents","DLQ Entry Contents",[14,1233,1234],{},"Each DLQ entry contains:",[722,1236,1237,1240,1257,1260,1263],{},[725,1238,1239],{},"Original webhook URL",[725,1241,1242,1243,254,1246,254,1249,254,1252,38,1254,1256],{},"Request headers and body (the same ",[35,1244,1245],{},"roomName",[35,1247,1248],{},"topicName",[35,1250,1251],{},"actorId",[35,1253,218],{},[35,1255,196],{}," that were sent)",[725,1258,1259],{},"Response status code (if received)",[725,1261,1262],{},"Error message",[725,1264,1265,1266,254,1269,1272,1273],{},"Timestamp, retry count and a status of ",[35,1267,1268],{},"pending",[35,1270,1271],{},"failed"," or ",[35,1274,1275],{},"dead",[57,1277,1279],{"id":1278},"viewing-the-dlq","Viewing the DLQ",[14,1281,1282],{},"The DLQ is exposed over the REST API only; there is no dashboard page for it yet. The endpoints are organization-scoped and authenticated with a user session token (the Bearer token the dashboard uses), not a project API key:",[73,1284,1286],{"className":803,"code":1285,"filename":805,"language":806,"meta":79,"style":79},"# List entries, optionally filtered by status (pending | failed | dead)\nGET /v1/organizations/{orgId}/webhook-dlq?status=pending&limit=50&offset=0\n\n# One entry\nGET /v1/organizations/{orgId}/webhook-dlq/{dlqId}\n\n# Mark an entry as permanently failed\nDELETE /v1/organizations/{orgId}/webhook-dlq/{dlqId}/mark-dead\n\n# Delete an entry\nDELETE /v1/organizations/{orgId}/webhook-dlq/{dlqId}\n\n# Count of pending entries, for monitoring\nGET /v1/organizations/{orgId}/webhook-dlq/stats/pending-count\n",[35,1287,1288,1293,1318,1322,1327,1334,1338,1343,1351,1355,1360,1366,1370,1375],{"__ignoreMap":79},[83,1289,1290],{"class":85,"line":86},[83,1291,1292],{"class":380},"# List entries, optionally filtered by status (pending | failed | dead)\n",[83,1294,1295,1298,1301,1304,1307,1310,1313,1315],{"class":85,"line":93},[83,1296,1297],{"class":347},"GET",[83,1299,1300],{"class":103}," /v1/organizations/{orgId}/webhook-dlq?status=pending",[83,1302,1303],{"class":89},"&limit",[83,1305,1306],{"class":371},"=",[83,1308,1309],{"class":103},"50",[83,1311,1312],{"class":89},"&offset",[83,1314,1306],{"class":371},[83,1316,1317],{"class":103},"0\n",[83,1319,1320],{"class":85,"line":110},[83,1321,408],{"emptyLinePlaceholder":407},[83,1323,1324],{"class":85,"line":123},[83,1325,1326],{"class":380},"# One entry\n",[83,1328,1329,1331],{"class":85,"line":136},[83,1330,1297],{"class":347},[83,1332,1333],{"class":103}," /v1/organizations/{orgId}/webhook-dlq/{dlqId}\n",[83,1335,1336],{"class":85,"line":145},[83,1337,408],{"emptyLinePlaceholder":407},[83,1339,1340],{"class":85,"line":158},[83,1341,1342],{"class":380},"# Mark an entry as permanently failed\n",[83,1344,1345,1348],{"class":85,"line":171},[83,1346,1347],{"class":347},"DELETE",[83,1349,1350],{"class":103}," /v1/organizations/{orgId}/webhook-dlq/{dlqId}/mark-dead\n",[83,1352,1353],{"class":85,"line":182},[83,1354,408],{"emptyLinePlaceholder":407},[83,1356,1357],{"class":85,"line":188},[83,1358,1359],{"class":380},"# Delete an entry\n",[83,1361,1362,1364],{"class":85,"line":677},[83,1363,1347],{"class":347},[83,1365,1333],{"class":103},[83,1367,1368],{"class":85,"line":688},[83,1369,408],{"emptyLinePlaceholder":407},[83,1371,1372],{"class":85,"line":697},[83,1373,1374],{"class":380},"# Count of pending entries, for monitoring\n",[83,1376,1377,1379],{"class":85,"line":702},[83,1378,1297],{"class":347},[83,1380,1381],{"class":103}," /v1/organizations/{orgId}/webhook-dlq/stats/pending-count\n",[14,1383,1384],{},"There is no retry endpoint. To replay a failed call, read the entry's request body and POST it to your endpoint yourself, then delete or mark the entry dead.",[52,1386,1388],{"id":1387},"best-practices","Best Practices",[722,1390,1391,1397,1403,1412,1418],{},[725,1392,1393,1396],{},[24,1394,1395],{},"Respond quickly"," - Webhook handlers should respond within a few seconds. Use async processing for heavy work.",[725,1398,1399,1402],{},[24,1400,1401],{},"Idempotency"," - Design your handlers to be idempotent since retries may cause duplicate calls.",[725,1404,1405,1408,1409,1411],{},[24,1406,1407],{},"Use scope for tenant isolation"," - Use the ",[35,1410,196],{}," field in webhook payloads to route data to the correct tenant.",[725,1413,1414,1417],{},[24,1415,1416],{},"Logging"," - Log incoming webhook requests for debugging and auditing.",[725,1419,1420,1423],{},[24,1421,1422],{},"Monitoring"," - Poll the DLQ pending count and alert when it grows; a failed trigger is otherwise silent.",[52,1425,1427],{"id":1426},"example-chat-application","Example: Chat Application",[14,1429,1430,1431,1433],{},"Here's a complete webhook handler example for a chat application. The hydration handler's JSON reply is what subscribers receive in their ",[35,1432,211],{}," event:",[70,1435,1436,1859,2120],{},[73,1437,1440],{"className":334,"code":1438,"filename":1439,"language":337,"meta":79,"style":79},"import express from 'express'\n\nconst app = express()\napp.use(express.json())\n\n// Hydration: Return recent messages when user joins\napp.post('/nolag/hydration', async (req, res) => {\n  const { actorId, roomName, topicName, scope } = req.body\n\n  // scope is null for unscoped actors, or:\n  // { accessScopeId: \"...\", slug: \"client-acme\", name: \"Acme Corp\" }\n  const tenantFilter = scope ? { tenantId: scope.accessScopeId } : {}\n\n  const messages = await db.messages.findMany({\n    where: { room: roomName, ...tenantFilter },\n    orderBy: { createdAt: 'desc' },\n    take: 50\n  })\n\n  res.json({ messages: messages.reverse() })\n})\n\n// Trigger: Store message and send notifications\napp.post('/nolag/trigger', async (req, res) => {\n  const { roomName, topicName, actorId, data, scope } = req.body\n\n  await db.messages.create({\n    data: {\n      room: roomName,\n      actorId,\n      content: data.text,\n      tenantId: scope?.accessScopeId\n    }\n  })\n\n  await pushService.notifyRoom(roomName, data)\n  res.status(200).send('OK')\n})\n","Express.js",[35,1441,1442,1454,1458,1473,1489,1493,1498,1532,1562,1566,1571,1576,1600,1604,1625,1636,1646,1654,1659,1663,1679,1683,1687,1692,1721,1752,1757,1770,1776,1782,1788,1794,1800,1805,1810,1815,1829,1854],{"__ignoreMap":79},[83,1443,1444,1446,1449,1451],{"class":85,"line":86},[83,1445,952],{"class":371},[83,1447,1448],{"class":89}," express ",[83,1450,958],{"class":371},[83,1452,1453],{"class":103}," 'express'\n",[83,1455,1456],{"class":85,"line":93},[83,1457,408],{"emptyLinePlaceholder":407},[83,1459,1460,1462,1465,1467,1470],{"class":85,"line":110},[83,1461,413],{"class":371},[83,1463,1464],{"class":96}," app",[83,1466,419],{"class":371},[83,1468,1469],{"class":347}," express",[83,1471,1472],{"class":89},"()\n",[83,1474,1475,1478,1481,1484,1486],{"class":85,"line":123},[83,1476,1477],{"class":89},"app.",[83,1479,1480],{"class":347},"use",[83,1482,1483],{"class":89},"(express.",[83,1485,78],{"class":347},[83,1487,1488],{"class":89},"())\n",[83,1490,1491],{"class":85,"line":136},[83,1492,408],{"emptyLinePlaceholder":407},[83,1494,1495],{"class":85,"line":145},[83,1496,1497],{"class":380},"// Hydration: Return recent messages when user joins\n",[83,1499,1500,1502,1505,1507,1510,1512,1515,1518,1521,1523,1526,1528,1530],{"class":85,"line":158},[83,1501,1477],{"class":89},[83,1503,1504],{"class":347},"post",[83,1506,351],{"class":89},[83,1508,1509],{"class":103},"'/nolag/hydration'",[83,1511,254],{"class":89},[83,1513,1514],{"class":371},"async",[83,1516,1517],{"class":89}," (",[83,1519,1520],{"class":360},"req",[83,1522,254],{"class":89},[83,1524,1525],{"class":360},"res",[83,1527,460],{"class":89},[83,1529,372],{"class":371},[83,1531,375],{"class":89},[83,1533,1534,1537,1540,1542,1544,1546,1548,1550,1552,1554,1557,1559],{"class":85,"line":171},[83,1535,1536],{"class":371},"  const",[83,1538,1539],{"class":89}," { ",[83,1541,1251],{"class":96},[83,1543,254],{"class":89},[83,1545,1245],{"class":96},[83,1547,254],{"class":89},[83,1549,1248],{"class":96},[83,1551,254],{"class":89},[83,1553,196],{"class":96},[83,1555,1556],{"class":89}," } ",[83,1558,1306],{"class":371},[83,1560,1561],{"class":89}," req.body\n",[83,1563,1564],{"class":85,"line":182},[83,1565,408],{"emptyLinePlaceholder":407},[83,1567,1568],{"class":85,"line":188},[83,1569,1570],{"class":380},"  // scope is null for unscoped actors, or:\n",[83,1572,1573],{"class":85,"line":677},[83,1574,1575],{"class":380},"  // { accessScopeId: \"...\", slug: \"client-acme\", name: \"Acme Corp\" }\n",[83,1577,1578,1580,1583,1585,1588,1591,1594,1597],{"class":85,"line":688},[83,1579,1536],{"class":371},[83,1581,1582],{"class":96}," tenantFilter",[83,1584,419],{"class":371},[83,1586,1587],{"class":89}," scope ",[83,1589,1590],{"class":371},"?",[83,1592,1593],{"class":89}," { tenantId: scope.accessScopeId } ",[83,1595,1596],{"class":371},":",[83,1598,1599],{"class":89}," {}\n",[83,1601,1602],{"class":85,"line":697},[83,1603,408],{"emptyLinePlaceholder":407},[83,1605,1606,1608,1611,1613,1616,1619,1622],{"class":85,"line":702},[83,1607,1536],{"class":371},[83,1609,1610],{"class":96}," messages",[83,1612,419],{"class":371},[83,1614,1615],{"class":371}," await",[83,1617,1618],{"class":89}," db.messages.",[83,1620,1621],{"class":347},"findMany",[83,1623,1624],{"class":89},"({\n",[83,1626,1627,1630,1633],{"class":85,"line":903},[83,1628,1629],{"class":89},"    where: { room: roomName, ",[83,1631,1632],{"class":371},"...",[83,1634,1635],{"class":89},"tenantFilter },\n",[83,1637,1638,1641,1644],{"class":85,"line":909},[83,1639,1640],{"class":89},"    orderBy: { createdAt: ",[83,1642,1643],{"class":103},"'desc'",[83,1645,275],{"class":89},[83,1647,1648,1651],{"class":85,"line":915},[83,1649,1650],{"class":89},"    take: ",[83,1652,1653],{"class":96},"50\n",[83,1655,1656],{"class":85,"line":921},[83,1657,1658],{"class":89},"  })\n",[83,1660,1661],{"class":85,"line":927},[83,1662,408],{"emptyLinePlaceholder":407},[83,1664,1665,1668,1670,1673,1676],{"class":85,"line":933},[83,1666,1667],{"class":89},"  res.",[83,1669,78],{"class":347},[83,1671,1672],{"class":89},"({ messages: messages.",[83,1674,1675],{"class":347},"reverse",[83,1677,1678],{"class":89},"() })\n",[83,1680,1681],{"class":85,"line":939},[83,1682,402],{"class":89},[83,1684,1685],{"class":85,"line":1133},[83,1686,408],{"emptyLinePlaceholder":407},[83,1688,1689],{"class":85,"line":1139},[83,1690,1691],{"class":380},"// Trigger: Store message and send notifications\n",[83,1693,1694,1696,1698,1700,1703,1705,1707,1709,1711,1713,1715,1717,1719],{"class":85,"line":1144},[83,1695,1477],{"class":89},[83,1697,1504],{"class":347},[83,1699,351],{"class":89},[83,1701,1702],{"class":103},"'/nolag/trigger'",[83,1704,254],{"class":89},[83,1706,1514],{"class":371},[83,1708,1517],{"class":89},[83,1710,1520],{"class":360},[83,1712,254],{"class":89},[83,1714,1525],{"class":360},[83,1716,460],{"class":89},[83,1718,372],{"class":371},[83,1720,375],{"class":89},[83,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750],{"class":85,"line":1723},25,[83,1725,1536],{"class":371},[83,1727,1539],{"class":89},[83,1729,1245],{"class":96},[83,1731,254],{"class":89},[83,1733,1248],{"class":96},[83,1735,254],{"class":89},[83,1737,1251],{"class":96},[83,1739,254],{"class":89},[83,1741,218],{"class":96},[83,1743,254],{"class":89},[83,1745,196],{"class":96},[83,1747,1556],{"class":89},[83,1749,1306],{"class":371},[83,1751,1561],{"class":89},[83,1753,1755],{"class":85,"line":1754},26,[83,1756,408],{"emptyLinePlaceholder":407},[83,1758,1760,1763,1765,1768],{"class":85,"line":1759},27,[83,1761,1762],{"class":371},"  await",[83,1764,1618],{"class":89},[83,1766,1767],{"class":347},"create",[83,1769,1624],{"class":89},[83,1771,1773],{"class":85,"line":1772},28,[83,1774,1775],{"class":89},"    data: {\n",[83,1777,1779],{"class":85,"line":1778},29,[83,1780,1781],{"class":89},"      room: roomName,\n",[83,1783,1785],{"class":85,"line":1784},30,[83,1786,1787],{"class":89},"      actorId,\n",[83,1789,1791],{"class":85,"line":1790},31,[83,1792,1793],{"class":89},"      content: data.text,\n",[83,1795,1797],{"class":85,"line":1796},32,[83,1798,1799],{"class":89},"      tenantId: scope?.accessScopeId\n",[83,1801,1803],{"class":85,"line":1802},33,[83,1804,1136],{"class":89},[83,1806,1808],{"class":85,"line":1807},34,[83,1809,1658],{"class":89},[83,1811,1813],{"class":85,"line":1812},35,[83,1814,408],{"emptyLinePlaceholder":407},[83,1816,1818,1820,1823,1826],{"class":85,"line":1817},36,[83,1819,1762],{"class":371},[83,1821,1822],{"class":89}," pushService.",[83,1824,1825],{"class":347},"notifyRoom",[83,1827,1828],{"class":89},"(roomName, data)\n",[83,1830,1832,1834,1837,1839,1842,1844,1847,1849,1852],{"class":85,"line":1831},37,[83,1833,1667],{"class":89},[83,1835,1836],{"class":347},"status",[83,1838,351],{"class":89},[83,1840,1841],{"class":96},"200",[83,1843,433],{"class":89},[83,1845,1846],{"class":347},"send",[83,1848,351],{"class":89},[83,1850,1851],{"class":103},"'OK'",[83,1853,444],{"class":89},[83,1855,1857],{"class":85,"line":1856},38,[83,1858,402],{"class":89},[73,1860,1863],{"className":466,"code":1861,"filename":1862,"language":469,"meta":79,"style":79},"from fastapi import FastAPI\nfrom pydantic import BaseModel\nfrom typing import Optional\n\napp = FastAPI()\n\nclass ScopeInfo(BaseModel):\n    accessScopeId: str\n    slug: str\n    name: str\n\nclass HydrationRequest(BaseModel):\n    actorId: str\n    roomName: str\n    topicName: str\n    scope: Optional[ScopeInfo] = None\n\nclass TriggerRequest(BaseModel):\n    roomName: str\n    topicName: str\n    actorId: str\n    data: dict\n    scope: Optional[ScopeInfo] = None\n\n# Hydration: Return recent messages when user joins\n@app.post('/nolag/hydration')\nasync def hydration(req: HydrationRequest):\n    filters = {'room': req.roomName}\n    if req.scope:\n        filters['tenant_id'] = req.scope.accessScopeId\n\n    messages = await db.messages.find_many(\n        where=filters,\n        order_by={'created_at': 'desc'},\n        take=50\n    )\n    return {'messages': list(reversed(messages))}\n\n# Trigger: Store message and send notifications\n@app.post('/nolag/trigger')\nasync def trigger(req: TriggerRequest):\n    await db.messages.create(\n        data={\n            'room': req.roomName,\n            'actor_id': req.actorId,\n            'content': req.data['text'],\n            'tenant_id': req.scope.accessScopeId if req.scope else None\n        }\n    )\n    await push_service.notify_room(req.roomName, req.data)\n    return {'status': 'ok'}\n","FastAPI",[35,1864,1865,1870,1875,1880,1884,1889,1893,1898,1903,1908,1913,1917,1922,1927,1932,1937,1942,1946,1951,1955,1959,1963,1968,1972,1976,1981,1986,1991,1996,2001,2006,2010,2015,2020,2025,2030,2035,2040,2044,2050,2056,2062,2068,2074,2080,2086,2092,2098,2103,2108,2114],{"__ignoreMap":79},[83,1866,1867],{"class":85,"line":86},[83,1868,1869],{},"from fastapi import FastAPI\n",[83,1871,1872],{"class":85,"line":93},[83,1873,1874],{},"from pydantic import BaseModel\n",[83,1876,1877],{"class":85,"line":110},[83,1878,1879],{},"from typing import Optional\n",[83,1881,1882],{"class":85,"line":123},[83,1883,408],{"emptyLinePlaceholder":407},[83,1885,1886],{"class":85,"line":136},[83,1887,1888],{},"app = FastAPI()\n",[83,1890,1891],{"class":85,"line":145},[83,1892,408],{"emptyLinePlaceholder":407},[83,1894,1895],{"class":85,"line":158},[83,1896,1897],{},"class ScopeInfo(BaseModel):\n",[83,1899,1900],{"class":85,"line":171},[83,1901,1902],{},"    accessScopeId: str\n",[83,1904,1905],{"class":85,"line":182},[83,1906,1907],{},"    slug: str\n",[83,1909,1910],{"class":85,"line":188},[83,1911,1912],{},"    name: str\n",[83,1914,1915],{"class":85,"line":677},[83,1916,408],{"emptyLinePlaceholder":407},[83,1918,1919],{"class":85,"line":688},[83,1920,1921],{},"class HydrationRequest(BaseModel):\n",[83,1923,1924],{"class":85,"line":697},[83,1925,1926],{},"    actorId: str\n",[83,1928,1929],{"class":85,"line":702},[83,1930,1931],{},"    roomName: str\n",[83,1933,1934],{"class":85,"line":903},[83,1935,1936],{},"    topicName: str\n",[83,1938,1939],{"class":85,"line":909},[83,1940,1941],{},"    scope: Optional[ScopeInfo] = None\n",[83,1943,1944],{"class":85,"line":915},[83,1945,408],{"emptyLinePlaceholder":407},[83,1947,1948],{"class":85,"line":921},[83,1949,1950],{},"class TriggerRequest(BaseModel):\n",[83,1952,1953],{"class":85,"line":927},[83,1954,1931],{},[83,1956,1957],{"class":85,"line":933},[83,1958,1936],{},[83,1960,1961],{"class":85,"line":939},[83,1962,1926],{},[83,1964,1965],{"class":85,"line":1133},[83,1966,1967],{},"    data: dict\n",[83,1969,1970],{"class":85,"line":1139},[83,1971,1941],{},[83,1973,1974],{"class":85,"line":1144},[83,1975,408],{"emptyLinePlaceholder":407},[83,1977,1978],{"class":85,"line":1723},[83,1979,1980],{},"# Hydration: Return recent messages when user joins\n",[83,1982,1983],{"class":85,"line":1754},[83,1984,1985],{},"@app.post('/nolag/hydration')\n",[83,1987,1988],{"class":85,"line":1759},[83,1989,1990],{},"async def hydration(req: HydrationRequest):\n",[83,1992,1993],{"class":85,"line":1772},[83,1994,1995],{},"    filters = {'room': req.roomName}\n",[83,1997,1998],{"class":85,"line":1778},[83,1999,2000],{},"    if req.scope:\n",[83,2002,2003],{"class":85,"line":1784},[83,2004,2005],{},"        filters['tenant_id'] = req.scope.accessScopeId\n",[83,2007,2008],{"class":85,"line":1790},[83,2009,408],{"emptyLinePlaceholder":407},[83,2011,2012],{"class":85,"line":1796},[83,2013,2014],{},"    messages = await db.messages.find_many(\n",[83,2016,2017],{"class":85,"line":1802},[83,2018,2019],{},"        where=filters,\n",[83,2021,2022],{"class":85,"line":1807},[83,2023,2024],{},"        order_by={'created_at': 'desc'},\n",[83,2026,2027],{"class":85,"line":1812},[83,2028,2029],{},"        take=50\n",[83,2031,2032],{"class":85,"line":1817},[83,2033,2034],{},"    )\n",[83,2036,2037],{"class":85,"line":1831},[83,2038,2039],{},"    return {'messages': list(reversed(messages))}\n",[83,2041,2042],{"class":85,"line":1856},[83,2043,408],{"emptyLinePlaceholder":407},[83,2045,2047],{"class":85,"line":2046},39,[83,2048,2049],{},"# Trigger: Store message and send notifications\n",[83,2051,2053],{"class":85,"line":2052},40,[83,2054,2055],{},"@app.post('/nolag/trigger')\n",[83,2057,2059],{"class":85,"line":2058},41,[83,2060,2061],{},"async def trigger(req: TriggerRequest):\n",[83,2063,2065],{"class":85,"line":2064},42,[83,2066,2067],{},"    await db.messages.create(\n",[83,2069,2071],{"class":85,"line":2070},43,[83,2072,2073],{},"        data={\n",[83,2075,2077],{"class":85,"line":2076},44,[83,2078,2079],{},"            'room': req.roomName,\n",[83,2081,2083],{"class":85,"line":2082},45,[83,2084,2085],{},"            'actor_id': req.actorId,\n",[83,2087,2089],{"class":85,"line":2088},46,[83,2090,2091],{},"            'content': req.data['text'],\n",[83,2093,2095],{"class":85,"line":2094},47,[83,2096,2097],{},"            'tenant_id': req.scope.accessScopeId if req.scope else None\n",[83,2099,2101],{"class":85,"line":2100},48,[83,2102,936],{},[83,2104,2106],{"class":85,"line":2105},49,[83,2107,2034],{},[83,2109,2111],{"class":85,"line":2110},50,[83,2112,2113],{},"    await push_service.notify_room(req.roomName, req.data)\n",[83,2115,2117],{"class":85,"line":2116},51,[83,2118,2119],{},"    return {'status': 'ok'}\n",[73,2121,2124],{"className":512,"code":2122,"filename":2123,"language":515,"meta":79,"style":79},"package main\n\nimport \"github.com/gin-gonic/gin\"\n\ntype ScopeInfo struct {\n    AccessScopeID string `json:\"accessScopeId\"`\n    Slug          string `json:\"slug\"`\n    Name          string `json:\"name\"`\n}\n\ntype HydrationRequest struct {\n    ActorID   string     `json:\"actorId\"`\n    RoomName  string     `json:\"roomName\"`\n    TopicName string     `json:\"topicName\"`\n    Scope     *ScopeInfo `json:\"scope\"`\n}\n\ntype TriggerRequest struct {\n    RoomName  string                 `json:\"roomName\"`\n    TopicName string                 `json:\"topicName\"`\n    ActorID   string                 `json:\"actorId\"`\n    Data      map[string]interface{} `json:\"data\"`\n    Scope     *ScopeInfo             `json:\"scope\"`\n}\n\nfunc main() {\n    r := gin.Default()\n\n    // Hydration: Return recent messages\n    r.POST(\"/nolag/hydration\", func(c *gin.Context) {\n        var req HydrationRequest\n        c.BindJSON(&req)\n\n        tenantID := \"\"\n        if req.Scope != nil {\n            tenantID = req.Scope.AccessScopeID\n        }\n\n        messages := db.GetRecentMessages(req.RoomName, 50, tenantID)\n        c.JSON(200, gin.H{\"messages\": messages})\n    })\n\n    // Trigger: Store message and notify\n    r.POST(\"/nolag/trigger\", func(c *gin.Context) {\n        var req TriggerRequest\n        c.BindJSON(&req)\n\n        db.CreateMessage(req.RoomName, req.ActorID, req.Data, req.Scope)\n        pushService.NotifyRoom(req.RoomName, req.Data)\n        c.String(200, \"OK\")\n    })\n\n    r.Run(\":8080\")\n}\n","Gin",[35,2125,2126,2131,2135,2140,2144,2149,2154,2159,2164,2168,2172,2177,2182,2187,2192,2197,2201,2205,2210,2215,2220,2225,2230,2235,2239,2243,2248,2253,2257,2262,2267,2272,2277,2281,2286,2291,2296,2300,2304,2309,2314,2319,2323,2328,2333,2338,2342,2346,2351,2356,2361,2365,2370,2376],{"__ignoreMap":79},[83,2127,2128],{"class":85,"line":86},[83,2129,2130],{},"package main\n",[83,2132,2133],{"class":85,"line":93},[83,2134,408],{"emptyLinePlaceholder":407},[83,2136,2137],{"class":85,"line":110},[83,2138,2139],{},"import \"github.com/gin-gonic/gin\"\n",[83,2141,2142],{"class":85,"line":123},[83,2143,408],{"emptyLinePlaceholder":407},[83,2145,2146],{"class":85,"line":136},[83,2147,2148],{},"type ScopeInfo struct {\n",[83,2150,2151],{"class":85,"line":145},[83,2152,2153],{},"    AccessScopeID string `json:\"accessScopeId\"`\n",[83,2155,2156],{"class":85,"line":158},[83,2157,2158],{},"    Slug          string `json:\"slug\"`\n",[83,2160,2161],{"class":85,"line":171},[83,2162,2163],{},"    Name          string `json:\"name\"`\n",[83,2165,2166],{"class":85,"line":182},[83,2167,191],{},[83,2169,2170],{"class":85,"line":188},[83,2171,408],{"emptyLinePlaceholder":407},[83,2173,2174],{"class":85,"line":677},[83,2175,2176],{},"type HydrationRequest struct {\n",[83,2178,2179],{"class":85,"line":688},[83,2180,2181],{},"    ActorID   string     `json:\"actorId\"`\n",[83,2183,2184],{"class":85,"line":697},[83,2185,2186],{},"    RoomName  string     `json:\"roomName\"`\n",[83,2188,2189],{"class":85,"line":702},[83,2190,2191],{},"    TopicName string     `json:\"topicName\"`\n",[83,2193,2194],{"class":85,"line":903},[83,2195,2196],{},"    Scope     *ScopeInfo `json:\"scope\"`\n",[83,2198,2199],{"class":85,"line":909},[83,2200,191],{},[83,2202,2203],{"class":85,"line":915},[83,2204,408],{"emptyLinePlaceholder":407},[83,2206,2207],{"class":85,"line":921},[83,2208,2209],{},"type TriggerRequest struct {\n",[83,2211,2212],{"class":85,"line":927},[83,2213,2214],{},"    RoomName  string                 `json:\"roomName\"`\n",[83,2216,2217],{"class":85,"line":933},[83,2218,2219],{},"    TopicName string                 `json:\"topicName\"`\n",[83,2221,2222],{"class":85,"line":939},[83,2223,2224],{},"    ActorID   string                 `json:\"actorId\"`\n",[83,2226,2227],{"class":85,"line":1133},[83,2228,2229],{},"    Data      map[string]interface{} `json:\"data\"`\n",[83,2231,2232],{"class":85,"line":1139},[83,2233,2234],{},"    Scope     *ScopeInfo             `json:\"scope\"`\n",[83,2236,2237],{"class":85,"line":1144},[83,2238,191],{},[83,2240,2241],{"class":85,"line":1723},[83,2242,408],{"emptyLinePlaceholder":407},[83,2244,2245],{"class":85,"line":1754},[83,2246,2247],{},"func main() {\n",[83,2249,2250],{"class":85,"line":1759},[83,2251,2252],{},"    r := gin.Default()\n",[83,2254,2255],{"class":85,"line":1772},[83,2256,408],{"emptyLinePlaceholder":407},[83,2258,2259],{"class":85,"line":1778},[83,2260,2261],{},"    // Hydration: Return recent messages\n",[83,2263,2264],{"class":85,"line":1784},[83,2265,2266],{},"    r.POST(\"/nolag/hydration\", func(c *gin.Context) {\n",[83,2268,2269],{"class":85,"line":1790},[83,2270,2271],{},"        var req HydrationRequest\n",[83,2273,2274],{"class":85,"line":1796},[83,2275,2276],{},"        c.BindJSON(&req)\n",[83,2278,2279],{"class":85,"line":1802},[83,2280,408],{"emptyLinePlaceholder":407},[83,2282,2283],{"class":85,"line":1807},[83,2284,2285],{},"        tenantID := \"\"\n",[83,2287,2288],{"class":85,"line":1812},[83,2289,2290],{},"        if req.Scope != nil {\n",[83,2292,2293],{"class":85,"line":1817},[83,2294,2295],{},"            tenantID = req.Scope.AccessScopeID\n",[83,2297,2298],{"class":85,"line":1831},[83,2299,936],{},[83,2301,2302],{"class":85,"line":1856},[83,2303,408],{"emptyLinePlaceholder":407},[83,2305,2306],{"class":85,"line":2046},[83,2307,2308],{},"        messages := db.GetRecentMessages(req.RoomName, 50, tenantID)\n",[83,2310,2311],{"class":85,"line":2052},[83,2312,2313],{},"        c.JSON(200, gin.H{\"messages\": messages})\n",[83,2315,2316],{"class":85,"line":2058},[83,2317,2318],{},"    })\n",[83,2320,2321],{"class":85,"line":2064},[83,2322,408],{"emptyLinePlaceholder":407},[83,2324,2325],{"class":85,"line":2070},[83,2326,2327],{},"    // Trigger: Store message and notify\n",[83,2329,2330],{"class":85,"line":2076},[83,2331,2332],{},"    r.POST(\"/nolag/trigger\", func(c *gin.Context) {\n",[83,2334,2335],{"class":85,"line":2082},[83,2336,2337],{},"        var req TriggerRequest\n",[83,2339,2340],{"class":85,"line":2088},[83,2341,2276],{},[83,2343,2344],{"class":85,"line":2094},[83,2345,408],{"emptyLinePlaceholder":407},[83,2347,2348],{"class":85,"line":2100},[83,2349,2350],{},"        db.CreateMessage(req.RoomName, req.ActorID, req.Data, req.Scope)\n",[83,2352,2353],{"class":85,"line":2105},[83,2354,2355],{},"        pushService.NotifyRoom(req.RoomName, req.Data)\n",[83,2357,2358],{"class":85,"line":2110},[83,2359,2360],{},"        c.String(200, \"OK\")\n",[83,2362,2363],{"class":85,"line":2116},[83,2364,2318],{},[83,2366,2368],{"class":85,"line":2367},52,[83,2369,408],{"emptyLinePlaceholder":407},[83,2371,2373],{"class":85,"line":2372},53,[83,2374,2375],{},"    r.Run(\":8080\")\n",[83,2377,2379],{"class":85,"line":2378},54,[83,2380,191],{},[52,2382,2384],{"id":2383},"next-steps","Next Steps",[722,2386,2387,2394,2400],{},[725,2388,2389],{},[2390,2391,2393],"a",{"href":2392},"/docs/concepts/qos","Quality of Service",[725,2395,2396],{},[2390,2397,2399],{"href":2398},"/docs/concepts/acl","Access Control",[725,2401,2402,2406,2407,2409],{},[2390,2403,2405],{"href":2404},"/docs/scopes","Access Scopes"," for the ",[35,2408,196],{}," object in payloads",[2411,2412,2413],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":79,"searchDepth":93,"depth":93,"links":2415},[2416,2420,2421,2425,2426,2427,2431,2432,2433],{"id":54,"depth":93,"text":55,"children":2417},[2418,2419],{"id":59,"depth":110,"text":60},{"id":576,"depth":110,"text":577},{"id":713,"depth":93,"text":714},{"id":755,"depth":93,"text":756,"children":2422},[2423,2424],{"id":759,"depth":110,"text":760},{"id":781,"depth":110,"text":782},{"id":1152,"depth":93,"text":1153},{"id":1191,"depth":93,"text":1192},{"id":1219,"depth":93,"text":1220,"children":2428},[2429,2430],{"id":1230,"depth":110,"text":1231},{"id":1278,"depth":110,"text":1279},{"id":1387,"depth":93,"text":1388},{"id":1426,"depth":93,"text":1427},{"id":2383,"depth":93,"text":2384},"Learn how to use NoLag webhooks for hydration and triggers to integrate with external systems.","md",{},"/docs/concepts/webhooks",{"title":5,"description":2434},"docs/concepts/webhooks","RsOwCgKuVpFcHLRzjDsS5NTKZLs3OBNj8x2rVgnj9cc",1789869419690]