[{"data":1,"prerenderedAt":3952},["ShallowReactive",2],{"navigation":3,"/features/webhooks":283,"/features/webhooks-surround":3947},[4,16,20,68,128,168,179,259,263,267,271,275,279],{"title":5,"path":6,"stem":7,"children":8},"Getting Started","/getting-started","1.getting-started",[9,12],{"title":10,"path":6,"stem":11},"Introduction","1.getting-started/index",{"title":13,"path":14,"stem":15},"Installation","/getting-started/installation","1.getting-started/1.installation",{"title":17,"path":18,"stem":19},"Upgrading","/upgrading","10.upgrading",{"title":21,"path":22,"stem":23,"children":24},"Essentials","/essentials","2.essentials",[25,28,32,36,40,44,48,52,56,60,64],{"title":26,"path":22,"stem":27},"Core concepts","2.essentials/index",{"title":29,"path":30,"stem":31},"Routing","/essentials/routing","2.essentials/1.routing",{"title":33,"path":34,"stem":35},"Typed Requests & Responses","/essentials/typed-io","2.essentials/10.typed-io",{"title":37,"path":38,"stem":39},"Authentication","/essentials/authentication","2.essentials/2.authentication",{"title":41,"path":42,"stem":43},"Identity & User Store","/essentials/identity","2.essentials/3.identity",{"title":45,"path":46,"stem":47},"Requests & Responses","/essentials/requests-responses","2.essentials/4.requests-responses",{"title":49,"path":50,"stem":51},"Controllers","/essentials/controllers","2.essentials/5.controllers",{"title":53,"path":54,"stem":55},"Database","/essentials/database","2.essentials/6.database",{"title":57,"path":58,"stem":59},"Validation","/essentials/validation","2.essentials/7.validation",{"title":61,"path":62,"stem":63},"Migrations","/essentials/migrations","2.essentials/8.migrations",{"title":65,"path":66,"stem":67},"ORM Models","/essentials/orm","2.essentials/9.orm",{"title":69,"path":70,"stem":71,"children":72},"Features","/features","3.features",[73,76,80,84,88,92,96,100,104,108,112,116,120,124],{"title":74,"path":70,"stem":75},"Built‑in Capabilities","3.features/index",{"title":77,"path":78,"stem":79},"API Keys & Scopes","/features/api-keys","3.features/api-keys",{"title":81,"path":82,"stem":83},"Browser Sessions (Cookie Auth)","/features/browser-sessions","3.features/browser-sessions",{"title":85,"path":86,"stem":87},"Caching","/features/caching","3.features/caching",{"title":89,"path":90,"stem":91},"CORS & CSRF","/features/cors-csrf","3.features/cors-csrf",{"title":93,"path":94,"stem":95},"Distributed Locks","/features/distributed-locks","3.features/distributed-locks",{"title":97,"path":98,"stem":99},"Events & Listeners","/features/events","3.features/events",{"title":101,"path":102,"stem":103},"Field Selection","/features/field-selection","3.features/field-selection",{"title":105,"path":106,"stem":107},"File Uploads","/features/file-uploads","3.features/file-uploads",{"title":109,"path":110,"stem":111},"Notifications","/features/notifications","3.features/notifications",{"title":113,"path":114,"stem":115},"Queues & Jobs","/features/queues-jobs","3.features/queues-jobs",{"title":117,"path":118,"stem":119},"Rate Limiting","/features/rate-limiting","3.features/rate-limiting",{"title":121,"path":122,"stem":123},"Task Scheduling","/features/scheduling","3.features/scheduling",{"title":125,"path":126,"stem":127},"Webhooks","/features/webhooks","3.features/webhooks",{"title":129,"path":130,"stem":131,"children":132},"Advanced","/advanced","4.advanced",[133,136,140,144,148,152,156,160,164],{"title":134,"path":130,"stem":135},"Techniques and Patterns","4.advanced/index",{"title":137,"path":138,"stem":139},"Configuration","/advanced/configuration","4.advanced/configuration",{"title":141,"path":142,"stem":143},"Dependency Injection","/advanced/dependency-injection","4.advanced/dependency-injection",{"title":145,"path":146,"stem":147},"Middleware","/advanced/middleware","4.advanced/middleware",{"title":149,"path":150,"stem":151},"OpenAPI Generation","/advanced/openapi","4.advanced/openapi",{"title":153,"path":154,"stem":155},"Performance","/advanced/performance","4.advanced/performance",{"title":157,"path":158,"stem":159},"Repositories","/advanced/repositories","4.advanced/repositories",{"title":161,"path":162,"stem":163},"Service Providers","/advanced/service-providers","4.advanced/service-providers",{"title":165,"path":166,"stem":167},"Testing","/advanced/testing","4.advanced/testing",{"title":169,"path":170,"stem":171,"children":172},"Overview","/tooling","5.tooling",[173,175],{"title":169,"path":170,"stem":174},"5.tooling/index",{"title":176,"path":177,"stem":178},"AI Skills","/tooling/ai-skills","5.tooling/1.ai-skills",{"title":180,"path":181,"stem":182,"children":183},"Cookbook","/cookbook","6.cookbook",[184,187,191,195,199,203,207,211,215,219,223,227,231,235,239,243,247,251,255],{"title":185,"path":181,"stem":186},"Recipes & How‑Tos","6.cookbook/index",{"title":188,"path":189,"stem":190},"Routing Recipes","/cookbook/routing","6.cookbook/1.routing",{"title":192,"path":193,"stem":194},"Caching Recipes","/cookbook/caching","6.cookbook/10.caching",{"title":196,"path":197,"stem":198},"Queue Infrastructure","/cookbook/queues-and-jobs","6.cookbook/11.queues-and-jobs",{"title":200,"path":201,"stem":202},"Notification Channels & Templates","/cookbook/notifications","6.cookbook/13.notifications",{"title":204,"path":205,"stem":206},"Storage","/cookbook/storage","6.cookbook/15.storage",{"title":208,"path":209,"stem":210},"Image Processing Examples","/cookbook/image-processing","6.cookbook/16.image-processing",{"title":212,"path":213,"stem":214},"Permissions and Authorization","/cookbook/permissions-and-authorization","6.cookbook/18.permissions-and-authorization",{"title":216,"path":217,"stem":218},"Session Analytics","/cookbook/sessions-analytics","6.cookbook/19.sessions-analytics",{"title":220,"path":221,"stem":222},"Writing Middleware","/cookbook/middleware","6.cookbook/2.middleware",{"title":224,"path":225,"stem":226},"API Metrics","/cookbook/api-metrics","6.cookbook/20.api-metrics",{"title":228,"path":229,"stem":230},"Performance Recipes","/cookbook/performance","6.cookbook/21.performance",{"title":232,"path":233,"stem":234},"Memory Management","/cookbook/memory-management","6.cookbook/22.memory-management",{"title":236,"path":237,"stem":238},"Writing Console Commands","/cookbook/console-commands","6.cookbook/23.console-commands",{"title":240,"path":241,"stem":242},"Glueful Extensions","/cookbook/extensions","6.cookbook/25.extensions",{"title":244,"path":245,"stem":246},"Error Handling","/cookbook/error-handling","6.cookbook/4.error-handling",{"title":248,"path":249,"stem":250},"Security Guide","/cookbook/security","6.cookbook/5.security",{"title":252,"path":253,"stem":254},"Service Options Resolver","/cookbook/configuration","6.cookbook/6.configuration",{"title":256,"path":257,"stem":258},"Logging","/cookbook/logging","6.cookbook/7.logging",{"title":260,"path":261,"stem":262},"API Reference","/api-reference","7.api-reference",{"title":264,"path":265,"stem":266},"CLI Reference","/cli-reference","8.cli-reference",{"title":268,"path":269,"stem":270},"Extensions","/extensions","9.extensions",{"title":272,"path":273,"stem":274},"Contributing","/contributing","contributing",{"title":276,"path":277,"stem":278},"Release Notes","/releases","releases",{"title":280,"path":281,"stem":282},"Release Archive","/releases-archive","releases-archive",{"id":284,"title":125,"body":285,"description":3941,"extension":3942,"links":3943,"meta":3944,"navigation":391,"path":126,"seo":3945,"stem":127,"__hash__":3946},"docs/3.features/webhooks.md",{"type":286,"value":287,"toc":3916},"minimark",[288,302,311,316,591,636,639,650,912,933,936,952,1036,1051,1055,1058,1292,1310,1315,1366,1370,1540,1546,1550,1560,1791,1813,1820,1924,1931,1934,1937,2064,2082,2085,2110,2452,2485,2492,2508,2512,2518,2560,2563,2567,2609,2613,2616,2644,2650,2654,2669,2677,2687,2710,2726,2987,2998,3001,3016,3070,3090,3093,3168,3172,3261,3397,3401,3427,3610,3643,3653,3657,3768,3783,3787,3809,3827,3850,3853,3885,3889,3912],[289,290,291,292,296,297,301],"p",{},"Glueful's webhooks are ",[293,294,295],"strong",{},"outbound",". Your app dispatches named events such as ",[298,299,300],"code",{},"order.shipped",", and the framework POSTs a signed JSON payload to every subscribed endpoint, on a queue, with retries. Subscriptions and delivery records live in two database tables. CLI commands, a management API and a scheduled cleanup job come with them.",[289,303,304,305,310],{},"There's no built-in receiver for third-party webhooks (Stripe, GitHub, …). Write that as an ordinary route that verifies the sender's own signature. See ",[306,307,309],"a",{"href":308},"#receiving-webhooks","Receiving Webhooks",".",[312,313,315],"h2",{"id":314},"quick-start","Quick Start",[317,318,323],"pre",{"className":319,"code":320,"language":321,"meta":322,"style":322},"language-php shiki shiki-themes material-theme-lighter github-light github-dark monokai","use Glueful\\Api\\Webhooks\\Webhook;\nuse Glueful\\Api\\Webhooks\\Contracts\\WebhookDispatcherInterface;\n\n// 1. Subscribe an endpoint (once, e.g. from an admin action)\n$subscription = Webhook::subscribe(['order.*'], 'https://partner.example.com/hooks');\n$secret = $subscription->secret; // whsec_..., give it to the receiver\n\n// 2. Dispatch an event wherever it happens\napp($context, WebhookDispatcherInterface::class)->dispatch('order.shipped', [\n    'order_id' => $order->uuid,\n    'carrier'  => 'ups',\n]);\n","php","",[298,324,325,359,386,393,400,452,480,485,491,536,563,585],{"__ignoreMap":322},[326,327,330,334,338,342,345,347,349,351,355],"span",{"class":328,"line":329},"line",1,[326,331,333],{"class":332},"shWJe","use",[326,335,337],{"class":336},"s91G_"," Glueful",[326,339,341],{"class":340},"sv8o3","\\",[326,343,344],{"class":336},"Api",[326,346,341],{"class":340},[326,348,125],{"class":336},[326,350,341],{"class":340},[326,352,354],{"class":353},"seZir","Webhook",[326,356,358],{"class":357},"swvn1",";\n",[326,360,362,364,366,368,370,372,374,376,379,381,384],{"class":328,"line":361},2,[326,363,333],{"class":332},[326,365,337],{"class":336},[326,367,341],{"class":340},[326,369,344],{"class":336},[326,371,341],{"class":340},[326,373,125],{"class":336},[326,375,341],{"class":340},[326,377,378],{"class":336},"Contracts",[326,380,341],{"class":340},[326,382,383],{"class":353},"WebhookDispatcherInterface",[326,385,358],{"class":357},[326,387,389],{"class":328,"line":388},3,[326,390,392],{"emptyLinePlaceholder":391},true,"\n",[326,394,396],{"class":328,"line":395},4,[326,397,399],{"class":398},"ss7Ak","// 1. Subscribe an endpoint (once, e.g. from an admin action)\n",[326,401,403,406,410,414,418,421,425,428,432,436,438,441,444,447,449],{"class":328,"line":402},5,[326,404,405],{"class":357},"$",[326,407,409],{"class":408},"ss--_","subscription ",[326,411,413],{"class":412},"sGXK2","=",[326,415,417],{"class":416},"s_MOj"," Webhook",[326,419,420],{"class":412},"::",[326,422,424],{"class":423},"sD0ED","subscribe",[326,426,427],{"class":357},"([",[326,429,431],{"class":430},"siCPE","'",[326,433,435],{"class":434},"sLACW","order.*",[326,437,431],{"class":430},[326,439,440],{"class":357},"],",[326,442,443],{"class":430}," '",[326,445,446],{"class":434},"https://partner.example.com/hooks",[326,448,431],{"class":430},[326,450,451],{"class":357},");\n",[326,453,455,457,460,462,465,468,471,474,477],{"class":328,"line":454},6,[326,456,405],{"class":357},[326,458,459],{"class":408},"secret ",[326,461,413],{"class":412},[326,463,464],{"class":357}," $",[326,466,467],{"class":408},"subscription",[326,469,470],{"class":412},"->",[326,472,473],{"class":408},"secret",[326,475,476],{"class":357},";",[326,478,479],{"class":398}," // whsec_..., give it to the receiver\n",[326,481,483],{"class":328,"line":482},7,[326,484,392],{"emptyLinePlaceholder":391},[326,486,488],{"class":328,"line":487},8,[326,489,490],{"class":398},"// 2. Dispatch an event wherever it happens\n",[326,492,494,497,500,503,506,509,511,514,517,519,522,525,527,529,531,533],{"class":328,"line":493},9,[326,495,496],{"class":423},"app",[326,498,499],{"class":357},"($",[326,501,502],{"class":408},"context",[326,504,505],{"class":357},",",[326,507,508],{"class":416}," WebhookDispatcherInterface",[326,510,420],{"class":412},[326,512,513],{"class":332},"class",[326,515,516],{"class":357},")",[326,518,470],{"class":412},[326,520,521],{"class":423},"dispatch",[326,523,524],{"class":357},"(",[326,526,431],{"class":430},[326,528,300],{"class":434},[326,530,431],{"class":430},[326,532,505],{"class":357},[326,534,535],{"class":357}," [\n",[326,537,539,542,545,547,550,552,555,557,560],{"class":328,"line":538},10,[326,540,541],{"class":430},"    '",[326,543,544],{"class":434},"order_id",[326,546,431],{"class":430},[326,548,549],{"class":412}," =>",[326,551,464],{"class":357},[326,553,554],{"class":408},"order",[326,556,470],{"class":412},[326,558,559],{"class":408},"uuid",[326,561,562],{"class":357},",\n",[326,564,566,568,571,573,576,578,581,583],{"class":328,"line":565},11,[326,567,541],{"class":430},[326,569,570],{"class":434},"carrier",[326,572,431],{"class":430},[326,574,575],{"class":412},"  =>",[326,577,443],{"class":430},[326,579,580],{"class":434},"ups",[326,582,431],{"class":430},[326,584,562],{"class":357},[326,586,588],{"class":328,"line":587},12,[326,589,590],{"class":357},"]);\n",[317,592,596],{"className":593,"code":594,"language":595,"meta":322,"style":322},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","# 0. Create the webhook tables (once)\nphp glueful migrate:run\n\n# 3. Run a worker on the webhooks queue\nphp glueful queue:work --queue=webhooks\n","bash",[298,597,598,603,614,618,623],{"__ignoreMap":322},[326,599,600],{"class":328,"line":329},[326,601,602],{"class":398},"# 0. Create the webhook tables (once)\n",[326,604,605,608,611],{"class":328,"line":361},[326,606,321],{"class":607},"sR7ES",[326,609,610],{"class":434}," glueful",[326,612,613],{"class":434}," migrate:run\n",[326,615,616],{"class":328,"line":388},[326,617,392],{"emptyLinePlaceholder":391},[326,619,620],{"class":328,"line":395},[326,621,622],{"class":398},"# 3. Run a worker on the webhooks queue\n",[326,624,625,627,629,632],{"class":328,"line":402},[326,626,321],{"class":607},[326,628,610],{"class":434},[326,630,631],{"class":434}," queue:work",[326,633,635],{"class":634},"sFhLe"," --queue=webhooks\n",[312,637,137],{"id":638},"configuration",[289,640,641,642,645,646,649],{},"The ",[298,643,644],{},"webhooks"," block of ",[298,647,648],{},"config/api.php",":",[651,652,653,672],"table",{},[654,655,656],"thead",{},[657,658,659,663,666,669],"tr",{},[660,661,662],"th",{},"Key",[660,664,665],{},"Env",[660,667,668],{},"Default",[660,670,671],{},"Notes",[673,674,675,704,723,746,763,783,803,823,843,860,879,896],"tbody",{},[657,676,677,683,688,693],{},[678,679,680],"td",{},[298,681,682],{},"enabled",[678,684,685],{},[298,686,687],{},"WEBHOOKS_ENABLED",[678,689,690],{},[298,691,692],{},"true",[678,694,695,696,699,700,703],{},"When ",[298,697,698],{},"false",", ",[298,701,702],{},"dispatch()"," creates no deliveries",[657,705,706,711,716,720],{},[678,707,708],{},[298,709,710],{},"queue",[678,712,713],{},[298,714,715],{},"WEBHOOKS_QUEUE",[678,717,718],{},[298,719,644],{},[678,721,722],{},"Queue the delivery jobs are pushed to",[657,724,725,730,735,740],{},[678,726,727],{},[298,728,729],{},"connection",[678,731,732],{},[298,733,734],{},"WEBHOOKS_QUEUE_CONNECTION",[678,736,737],{},[298,738,739],{},"null",[678,741,742,743,745],{},"Queue connection for delivery jobs; ",[298,744,739],{}," uses the default connection",[657,747,748,753,755,760],{},[678,749,750],{},[298,751,752],{},"signature_header",[678,754],{},[678,756,757],{},[298,758,759],{},"X-Webhook-Signature",[678,761,762],{},"Header carrying the signature",[657,764,765,770,772,777],{},[678,766,767],{},[298,768,769],{},"signature_algorithm",[678,771],{},[678,773,774],{},[298,775,776],{},"sha256",[678,778,779,782],{},[298,780,781],{},"hash_hmac()"," algorithm used to sign",[657,784,785,790,795,800],{},[678,786,787],{},[298,788,789],{},"timeout",[678,791,792],{},[298,793,794],{},"WEBHOOKS_TIMEOUT",[678,796,797],{},[298,798,799],{},"30",[678,801,802],{},"Per-request timeout in seconds",[657,804,805,810,812,817],{},[678,806,807],{},[298,808,809],{},"user_agent",[678,811],{},[678,813,814],{},[298,815,816],{},"Glueful-Webhooks/1.0",[678,818,819,822],{},[298,820,821],{},"User-Agent"," header",[657,824,825,830,835,840],{},[678,826,827],{},[298,828,829],{},"retry.max_attempts",[678,831,832],{},[298,833,834],{},"WEBHOOKS_MAX_ATTEMPTS",[678,836,837],{},[298,838,839],{},"5",[678,841,842],{},"Total attempts before a delivery is marked failed",[657,844,845,850,852,857],{},[678,846,847],{},[298,848,849],{},"retry.backoff",[678,851],{},[678,853,854],{},[298,855,856],{},"[60, 300, 1800, 7200, 43200]",[678,858,859],{},"Delay in seconds after each failed attempt; the last value repeats",[657,861,862,867,872,876],{},[678,863,864],{},[298,865,866],{},"require_https",[678,868,869],{},[298,870,871],{},"WEBHOOKS_REQUIRE_HTTPS",[678,873,874],{},[298,875,692],{},[678,877,878],{},"Reject non-HTTPS subscription URLs",[657,880,881,886,888,893],{},[678,882,883],{},[298,884,885],{},"cleanup.keep_successful_days",[678,887],{},[678,889,890],{},[298,891,892],{},"7",[678,894,895],{},"Retention for delivered records",[657,897,898,903,905,909],{},[678,899,900],{},[298,901,902],{},"cleanup.keep_failed_days",[678,904],{},[678,906,907],{},[298,908,799],{},[678,910,911],{},"Retention for failed records",[289,913,914,916,917,920,921,924,925,928,929,310],{},[298,915,682],{}," also controls the bridge from application events: with ",[298,918,919],{},"api.webhooks.enabled"," and ",[298,922,923],{},"events.enabled"," both on, ",[298,926,927],{},"WebhookEventListener"," is registered at boot. See ",[306,930,932],{"href":931},"#from-application-events","From application events",[312,934,204],{"id":935},"storage",[289,937,938,939,941,942,699,945,948,949,310],{},"The two tables ship as the core ",[298,940,644],{}," migration leaf (",[298,943,944],{},"migrations/webhooks",[298,946,947],{},"glueful/framework:webhooks","). Create them with ",[298,950,951],{},"php glueful migrate:run",[651,953,954,964],{},[654,955,956],{},[657,957,958,961],{},[660,959,960],{},"Table",[660,962,963],{},"Holds",[673,965,966,998],{},[657,967,968,973],{},[678,969,970],{},[298,971,972],{},"webhook_subscriptions",[678,974,975,977,978,981,982,699,985,988,989,699,991,699,994,997],{},[298,976,559],{}," (",[298,979,980],{},"wh_sub_…","), ",[298,983,984],{},"url",[298,986,987],{},"events"," (JSON), ",[298,990,473],{},[298,992,993],{},"is_active",[298,995,996],{},"metadata"," (JSON)",[657,999,1000,1005],{},[678,1001,1002],{},[298,1003,1004],{},"webhook_deliveries",[678,1006,1007,699,1009,699,1012,699,1015,988,1018,699,1021,699,1024,699,1027,699,1030,699,1033],{},[298,1008,559],{},[298,1010,1011],{},"subscription_id",[298,1013,1014],{},"event",[298,1016,1017],{},"payload",[298,1019,1020],{},"status",[298,1022,1023],{},"attempts",[298,1025,1026],{},"response_code",[298,1028,1029],{},"response_body",[298,1031,1032],{},"delivered_at",[298,1034,1035],{},"next_retry_at",[1037,1038,1042],"u-alert",{"color":1039,"icon":1040,"variant":1041},"info","i-lucide-info","subtle",[289,1043,1044,1047,1048,1050],{},[293,1045,1046],{},"Since 1.91:"," the tables come from the migration; the dispatcher no longer creates them at runtime. Run ",[298,1049,951],{}," after upgrading. Databases whose webhook tables were created by the old runtime path migrate cleanly.",[312,1052,1054],{"id":1053},"subscriptions","Subscriptions",[289,1056,1057],{},"A subscription is an endpoint URL plus the event patterns it wants. Each subscription has its own signing secret.",[317,1059,1061],{"className":319,"code":1060,"language":321,"meta":322,"style":322},"use Glueful\\Api\\Webhooks\\Webhook;\nuse Glueful\\Api\\Webhooks\\WebhookSubscription;\n\n$sub = Webhook::subscribe(\n    ['order.created', 'order.shipped', 'customer.*'],\n    'https://partner.example.com/hooks',\n    ['owner' => 'partner-42'],               // metadata (optional)\n);\n\n// Equivalent, through the ORM model (context-first)\n$sub = WebhookSubscription::create($context, [\n    'url' => 'https://partner.example.com/hooks',\n    'events' => ['order.*'],\n    'is_active' => true,\n]);\n",[298,1062,1063,1083,1104,1108,1126,1158,1168,1193,1197,1201,1206,1230,1248,1270,1287],{"__ignoreMap":322},[326,1064,1065,1067,1069,1071,1073,1075,1077,1079,1081],{"class":328,"line":329},[326,1066,333],{"class":332},[326,1068,337],{"class":336},[326,1070,341],{"class":340},[326,1072,344],{"class":336},[326,1074,341],{"class":340},[326,1076,125],{"class":336},[326,1078,341],{"class":340},[326,1080,354],{"class":353},[326,1082,358],{"class":357},[326,1084,1085,1087,1089,1091,1093,1095,1097,1099,1102],{"class":328,"line":361},[326,1086,333],{"class":332},[326,1088,337],{"class":336},[326,1090,341],{"class":340},[326,1092,344],{"class":336},[326,1094,341],{"class":340},[326,1096,125],{"class":336},[326,1098,341],{"class":340},[326,1100,1101],{"class":353},"WebhookSubscription",[326,1103,358],{"class":357},[326,1105,1106],{"class":328,"line":388},[326,1107,392],{"emptyLinePlaceholder":391},[326,1109,1110,1112,1115,1117,1119,1121,1123],{"class":328,"line":395},[326,1111,405],{"class":357},[326,1113,1114],{"class":408},"sub ",[326,1116,413],{"class":412},[326,1118,417],{"class":416},[326,1120,420],{"class":412},[326,1122,424],{"class":423},[326,1124,1125],{"class":357},"(\n",[326,1127,1128,1131,1133,1136,1138,1140,1142,1144,1146,1148,1150,1153,1155],{"class":328,"line":402},[326,1129,1130],{"class":357},"    [",[326,1132,431],{"class":430},[326,1134,1135],{"class":434},"order.created",[326,1137,431],{"class":430},[326,1139,505],{"class":357},[326,1141,443],{"class":430},[326,1143,300],{"class":434},[326,1145,431],{"class":430},[326,1147,505],{"class":357},[326,1149,443],{"class":430},[326,1151,1152],{"class":434},"customer.*",[326,1154,431],{"class":430},[326,1156,1157],{"class":357},"],\n",[326,1159,1160,1162,1164,1166],{"class":328,"line":454},[326,1161,541],{"class":430},[326,1163,446],{"class":434},[326,1165,431],{"class":430},[326,1167,562],{"class":357},[326,1169,1170,1172,1174,1177,1179,1181,1183,1186,1188,1190],{"class":328,"line":482},[326,1171,1130],{"class":357},[326,1173,431],{"class":430},[326,1175,1176],{"class":434},"owner",[326,1178,431],{"class":430},[326,1180,549],{"class":412},[326,1182,443],{"class":430},[326,1184,1185],{"class":434},"partner-42",[326,1187,431],{"class":430},[326,1189,440],{"class":357},[326,1191,1192],{"class":398},"               // metadata (optional)\n",[326,1194,1195],{"class":328,"line":487},[326,1196,451],{"class":357},[326,1198,1199],{"class":328,"line":493},[326,1200,392],{"emptyLinePlaceholder":391},[326,1202,1203],{"class":328,"line":538},[326,1204,1205],{"class":398},"// Equivalent, through the ORM model (context-first)\n",[326,1207,1208,1210,1212,1214,1217,1219,1222,1224,1226,1228],{"class":328,"line":565},[326,1209,405],{"class":357},[326,1211,1114],{"class":408},[326,1213,413],{"class":412},[326,1215,1216],{"class":416}," WebhookSubscription",[326,1218,420],{"class":412},[326,1220,1221],{"class":423},"create",[326,1223,499],{"class":357},[326,1225,502],{"class":408},[326,1227,505],{"class":357},[326,1229,535],{"class":357},[326,1231,1232,1234,1236,1238,1240,1242,1244,1246],{"class":328,"line":587},[326,1233,541],{"class":430},[326,1235,984],{"class":434},[326,1237,431],{"class":430},[326,1239,549],{"class":412},[326,1241,443],{"class":430},[326,1243,446],{"class":434},[326,1245,431],{"class":430},[326,1247,562],{"class":357},[326,1249,1251,1253,1255,1257,1259,1262,1264,1266,1268],{"class":328,"line":1250},13,[326,1252,541],{"class":430},[326,1254,987],{"class":434},[326,1256,431],{"class":430},[326,1258,549],{"class":412},[326,1260,1261],{"class":357}," [",[326,1263,431],{"class":430},[326,1265,435],{"class":434},[326,1267,431],{"class":430},[326,1269,1157],{"class":357},[326,1271,1273,1275,1277,1279,1281,1285],{"class":328,"line":1272},14,[326,1274,541],{"class":430},[326,1276,993],{"class":434},[326,1278,431],{"class":430},[326,1280,549],{"class":412},[326,1282,1284],{"class":1283},"sMTiH"," true",[326,1286,562],{"class":357},[326,1288,1290],{"class":328,"line":1289},15,[326,1291,590],{"class":357},[289,1293,1294,1295,977,1297,1300,1301,977,1303,1306,1307,1309],{},"On creation, the model generates the ",[298,1296,559],{},[298,1298,1299],{},"wh_sub_"," + 16 characters) and a ",[298,1302,473],{},[298,1304,1305],{},"whsec_"," + 64 hex characters) when you don't supply them. ",[298,1308,473],{}," is hidden from the model's array/JSON output.",[1311,1312,1314],"h3",{"id":1313},"event-patterns","Event patterns",[651,1316,1317,1327],{},[654,1318,1319],{},[657,1320,1321,1324],{},[660,1322,1323],{},"Pattern",[660,1325,1326],{},"Matches",[673,1328,1329,1340,1356],{},[657,1330,1331,1335],{},[678,1332,1333],{},[298,1334,300],{},[678,1336,1337,1338],{},"Exactly ",[298,1339,300],{},[657,1341,1342,1346],{},[678,1343,1344],{},[298,1345,435],{},[678,1347,1348,699,1350,1352,1353,516],{},[298,1349,1135],{},[298,1351,300],{},", … (anything starting ",[298,1354,1355],{},"order.",[657,1357,1358,1363],{},[678,1359,1360],{},[298,1361,1362],{},"*",[678,1364,1365],{},"Every event",[1311,1367,1369],{"id":1368},"managing-a-subscription","Managing a subscription",[317,1371,1373],{"className":319,"code":1372,"language":321,"meta":322,"style":322},"$sub = Webhook::findSubscription('wh_sub_abc123');\n\n$sub->deactivate();             // stops new deliveries; queued ones fail\n$sub->activate();\n$newSecret = $sub->rotateSecret(); // takes effect immediately, with no overlap\n\n$sub->stats(30);       // ['total' => .., 'delivered' => .., 'failed' => .., 'pending' => ..]\n$sub->successRate(30); // percentage, 0-100\n\nWebhook::activeSubscriptions();\nWebhook::subscriptionsForEvent('order.shipped');\n",[298,1374,1375,1401,1405,1423,1437,1460,1464,1486,1506,1510,1521],{"__ignoreMap":322},[326,1376,1377,1379,1381,1383,1385,1387,1390,1392,1394,1397,1399],{"class":328,"line":329},[326,1378,405],{"class":357},[326,1380,1114],{"class":408},[326,1382,413],{"class":412},[326,1384,417],{"class":416},[326,1386,420],{"class":412},[326,1388,1389],{"class":423},"findSubscription",[326,1391,524],{"class":357},[326,1393,431],{"class":430},[326,1395,1396],{"class":434},"wh_sub_abc123",[326,1398,431],{"class":430},[326,1400,451],{"class":357},[326,1402,1403],{"class":328,"line":361},[326,1404,392],{"emptyLinePlaceholder":391},[326,1406,1407,1409,1412,1414,1417,1420],{"class":328,"line":388},[326,1408,405],{"class":357},[326,1410,1411],{"class":408},"sub",[326,1413,470],{"class":412},[326,1415,1416],{"class":423},"deactivate",[326,1418,1419],{"class":357},"();",[326,1421,1422],{"class":398},"             // stops new deliveries; queued ones fail\n",[326,1424,1425,1427,1429,1431,1434],{"class":328,"line":395},[326,1426,405],{"class":357},[326,1428,1411],{"class":408},[326,1430,470],{"class":412},[326,1432,1433],{"class":423},"activate",[326,1435,1436],{"class":357},"();\n",[326,1438,1439,1441,1444,1446,1448,1450,1452,1455,1457],{"class":328,"line":402},[326,1440,405],{"class":357},[326,1442,1443],{"class":408},"newSecret ",[326,1445,413],{"class":412},[326,1447,464],{"class":357},[326,1449,1411],{"class":408},[326,1451,470],{"class":412},[326,1453,1454],{"class":423},"rotateSecret",[326,1456,1419],{"class":357},[326,1458,1459],{"class":398}," // takes effect immediately, with no overlap\n",[326,1461,1462],{"class":328,"line":454},[326,1463,392],{"emptyLinePlaceholder":391},[326,1465,1466,1468,1470,1472,1475,1477,1480,1483],{"class":328,"line":482},[326,1467,405],{"class":357},[326,1469,1411],{"class":408},[326,1471,470],{"class":412},[326,1473,1474],{"class":423},"stats",[326,1476,524],{"class":357},[326,1478,799],{"class":1479},"sYThS",[326,1481,1482],{"class":357},");",[326,1484,1485],{"class":398},"       // ['total' => .., 'delivered' => .., 'failed' => .., 'pending' => ..]\n",[326,1487,1488,1490,1492,1494,1497,1499,1501,1503],{"class":328,"line":487},[326,1489,405],{"class":357},[326,1491,1411],{"class":408},[326,1493,470],{"class":412},[326,1495,1496],{"class":423},"successRate",[326,1498,524],{"class":357},[326,1500,799],{"class":1479},[326,1502,1482],{"class":357},[326,1504,1505],{"class":398}," // percentage, 0-100\n",[326,1507,1508],{"class":328,"line":493},[326,1509,392],{"emptyLinePlaceholder":391},[326,1511,1512,1514,1516,1519],{"class":328,"line":538},[326,1513,354],{"class":416},[326,1515,420],{"class":412},[326,1517,1518],{"class":423},"activeSubscriptions",[326,1520,1436],{"class":357},[326,1522,1523,1525,1527,1530,1532,1534,1536,1538],{"class":328,"line":565},[326,1524,354],{"class":416},[326,1526,420],{"class":412},[326,1528,1529],{"class":423},"subscriptionsForEvent",[326,1531,524],{"class":357},[326,1533,431],{"class":430},[326,1535,300],{"class":434},[326,1537,431],{"class":430},[326,1539,451],{"class":357},[289,1541,1542,1545],{},[298,1543,1544],{},"rotateSecret()"," replaces the secret at once, so share the new one with the receiver before rotating, or accept that deliveries fail verification until the receiver is updated.",[312,1547,1549],{"id":1548},"dispatching-events","Dispatching Events",[289,1551,1552,1553,1556,1557,649],{},"Inject or resolve ",[298,1554,1555],{},"Glueful\\Api\\Webhooks\\Contracts\\WebhookDispatcherInterface",", which is bound to ",[298,1558,1559],{},"WebhookDispatcher",[317,1561,1563],{"className":319,"code":1562,"language":321,"meta":322,"style":322},"use Glueful\\Api\\Webhooks\\Contracts\\WebhookDispatcherInterface;\n\nfinal class ShippingService\n{\n    public function __construct(private readonly WebhookDispatcherInterface $webhooks)\n    {\n    }\n\n    public function markShipped(Order $order): void\n    {\n        // ... update the order ...\n\n        $deliveries = $this->webhooks->dispatch('order.shipped', [\n            'order_id' => $order->uuid,\n            'shipped_at' => date('c'),\n        ]);\n    }\n}\n",[298,1564,1565,1589,1593,1607,1612,1641,1646,1651,1655,1680,1684,1689,1693,1727,1748,1774,1780,1785],{"__ignoreMap":322},[326,1566,1567,1569,1571,1573,1575,1577,1579,1581,1583,1585,1587],{"class":328,"line":329},[326,1568,333],{"class":332},[326,1570,337],{"class":336},[326,1572,341],{"class":340},[326,1574,344],{"class":336},[326,1576,341],{"class":340},[326,1578,125],{"class":336},[326,1580,341],{"class":340},[326,1582,378],{"class":336},[326,1584,341],{"class":340},[326,1586,383],{"class":353},[326,1588,358],{"class":357},[326,1590,1591],{"class":328,"line":361},[326,1592,392],{"emptyLinePlaceholder":391},[326,1594,1595,1599,1603],{"class":328,"line":388},[326,1596,1598],{"class":1597},"sTNss","final",[326,1600,1602],{"class":1601},"srJo8"," class",[326,1604,1606],{"class":1605},"sKvfc"," ShippingService\n",[326,1608,1609],{"class":328,"line":395},[326,1610,1611],{"class":357},"{\n",[326,1613,1614,1617,1620,1624,1626,1629,1632,1634,1636,1638],{"class":328,"line":402},[326,1615,1616],{"class":1597},"    public",[326,1618,1619],{"class":1601}," function",[326,1621,1623],{"class":1622},"sMLJd"," __construct",[326,1625,524],{"class":357},[326,1627,1628],{"class":1597},"private",[326,1630,1631],{"class":1597}," readonly",[326,1633,508],{"class":416},[326,1635,464],{"class":357},[326,1637,644],{"class":408},[326,1639,1640],{"class":357},")\n",[326,1642,1643],{"class":328,"line":454},[326,1644,1645],{"class":357},"    {\n",[326,1647,1648],{"class":328,"line":482},[326,1649,1650],{"class":357},"    }\n",[326,1652,1653],{"class":328,"line":487},[326,1654,392],{"emptyLinePlaceholder":391},[326,1656,1657,1659,1661,1664,1666,1669,1671,1673,1675,1677],{"class":328,"line":493},[326,1658,1616],{"class":1597},[326,1660,1619],{"class":1601},[326,1662,1663],{"class":423}," markShipped",[326,1665,524],{"class":357},[326,1667,1668],{"class":416},"Order",[326,1670,464],{"class":357},[326,1672,554],{"class":408},[326,1674,516],{"class":357},[326,1676,649],{"class":412},[326,1678,1679],{"class":332}," void\n",[326,1681,1682],{"class":328,"line":538},[326,1683,1645],{"class":357},[326,1685,1686],{"class":328,"line":565},[326,1687,1688],{"class":398},"        // ... update the order ...\n",[326,1690,1691],{"class":328,"line":587},[326,1692,392],{"emptyLinePlaceholder":391},[326,1694,1695,1698,1701,1703,1707,1709,1711,1713,1715,1717,1719,1721,1723,1725],{"class":328,"line":1250},[326,1696,1697],{"class":357},"        $",[326,1699,1700],{"class":408},"deliveries ",[326,1702,413],{"class":412},[326,1704,1706],{"class":1705},"sSBr1"," $this",[326,1708,470],{"class":412},[326,1710,644],{"class":408},[326,1712,470],{"class":412},[326,1714,521],{"class":423},[326,1716,524],{"class":357},[326,1718,431],{"class":430},[326,1720,300],{"class":434},[326,1722,431],{"class":430},[326,1724,505],{"class":357},[326,1726,535],{"class":357},[326,1728,1729,1732,1734,1736,1738,1740,1742,1744,1746],{"class":328,"line":1272},[326,1730,1731],{"class":430},"            '",[326,1733,544],{"class":434},[326,1735,431],{"class":430},[326,1737,549],{"class":412},[326,1739,464],{"class":357},[326,1741,554],{"class":408},[326,1743,470],{"class":412},[326,1745,559],{"class":408},[326,1747,562],{"class":357},[326,1749,1750,1752,1755,1757,1759,1762,1764,1766,1769,1771],{"class":328,"line":1289},[326,1751,1731],{"class":430},[326,1753,1754],{"class":434},"shipped_at",[326,1756,431],{"class":430},[326,1758,549],{"class":412},[326,1760,1761],{"class":1622}," date",[326,1763,524],{"class":357},[326,1765,431],{"class":430},[326,1767,1768],{"class":434},"c",[326,1770,431],{"class":430},[326,1772,1773],{"class":357},"),\n",[326,1775,1777],{"class":328,"line":1776},16,[326,1778,1779],{"class":357},"        ]);\n",[326,1781,1783],{"class":328,"line":1782},17,[326,1784,1650],{"class":357},[326,1786,1788],{"class":328,"line":1787},18,[326,1789,1790],{"class":357},"}\n",[289,1792,1793,1794,1796,1797,1800,1801,1804,1805,1808,1809,1812],{},"For each active subscription that matches the event, ",[298,1795,702],{}," creates a ",[298,1798,1799],{},"pending"," delivery record and queues a ",[298,1802,1803],{},"DeliverWebhookJob",". It returns the ",[298,1806,1807],{},"WebhookDelivery"," records and, when at least one was created, fires ",[298,1810,1811],{},"WebhookDispatchedEvent",". Nothing goes over HTTP inside the request.",[289,1814,1815,1816,1819],{},"The third ",[298,1817,1818],{},"$options"," argument overrides where and when the jobs run:",[317,1821,1823],{"className":319,"code":1822,"language":321,"meta":322,"style":322},"$this->webhooks->dispatch('order.shipped', $data, [\n    'queue' => 'webhooks-priority', // default: api.webhooks.queue\n    'connection' => 'redis',        // default: api.webhooks.connection, then the default connection\n    'delay' => 30,                  // seconds before the first attempt\n]);\n",[298,1824,1825,1857,1879,1901,1920],{"__ignoreMap":322},[326,1826,1827,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1853,1855],{"class":328,"line":329},[326,1828,1829],{"class":1705},"$this",[326,1831,470],{"class":412},[326,1833,644],{"class":408},[326,1835,470],{"class":412},[326,1837,521],{"class":423},[326,1839,524],{"class":357},[326,1841,431],{"class":430},[326,1843,300],{"class":434},[326,1845,431],{"class":430},[326,1847,505],{"class":357},[326,1849,464],{"class":357},[326,1851,1852],{"class":408},"data",[326,1854,505],{"class":357},[326,1856,535],{"class":357},[326,1858,1859,1861,1863,1865,1867,1869,1872,1874,1876],{"class":328,"line":361},[326,1860,541],{"class":430},[326,1862,710],{"class":434},[326,1864,431],{"class":430},[326,1866,549],{"class":412},[326,1868,443],{"class":430},[326,1870,1871],{"class":434},"webhooks-priority",[326,1873,431],{"class":430},[326,1875,505],{"class":357},[326,1877,1878],{"class":398}," // default: api.webhooks.queue\n",[326,1880,1881,1883,1885,1887,1889,1891,1894,1896,1898],{"class":328,"line":388},[326,1882,541],{"class":430},[326,1884,729],{"class":434},[326,1886,431],{"class":430},[326,1888,549],{"class":412},[326,1890,443],{"class":430},[326,1892,1893],{"class":434},"redis",[326,1895,431],{"class":430},[326,1897,505],{"class":357},[326,1899,1900],{"class":398},"        // default: api.webhooks.connection, then the default connection\n",[326,1902,1903,1905,1908,1910,1912,1915,1917],{"class":328,"line":395},[326,1904,541],{"class":430},[326,1906,1907],{"class":434},"delay",[326,1909,431],{"class":430},[326,1911,549],{"class":412},[326,1913,1914],{"class":1479}," 30",[326,1916,505],{"class":357},[326,1918,1919],{"class":398},"                  // seconds before the first attempt\n",[326,1921,1922],{"class":328,"line":402},[326,1923,590],{"class":357},[289,1925,1926,1927,1930],{},"The static ",[298,1928,1929],{},"Webhook::dispatch()"," does the same thing. Its context is set during HTTP boot (and by the webhook commands), so in queue workers or your own CLI commands, use the injected dispatcher.",[1311,1932,1933],{"id":1017},"Payload",[289,1935,1936],{},"Every delivery body has the same envelope:",[317,1938,1942],{"className":1939,"code":1940,"language":1941,"meta":322,"style":322},"language-json shiki shiki-themes material-theme-lighter github-light github-dark monokai","{\n  \"id\": \"wh_evt_V1StGXR8Z5jdHi6BmyT2\",\n  \"event\": \"order.shipped\",\n  \"created_at\": \"2026-10-27T14:03:11+00:00\",\n  \"data\": { \"order_id\": \"ord_123\", \"shipped_at\": \"2026-10-27T14:03:11+00:00\" }\n}\n","json",[298,1943,1944,1948,1975,1993,2013,2060],{"__ignoreMap":322},[326,1945,1946],{"class":328,"line":329},[326,1947,1611],{"class":357},[326,1949,1950,1954,1958,1961,1963,1967,1971,1973],{"class":328,"line":361},[326,1951,1953],{"class":1952},"saDeg","  \"",[326,1955,1957],{"class":1956},"sEff5","id",[326,1959,1960],{"class":1952},"\"",[326,1962,649],{"class":357},[326,1964,1966],{"class":1965},"sh1VR"," \"",[326,1968,1970],{"class":1969},"sINAO","wh_evt_V1StGXR8Z5jdHi6BmyT2",[326,1972,1960],{"class":1965},[326,1974,562],{"class":357},[326,1976,1977,1979,1981,1983,1985,1987,1989,1991],{"class":328,"line":388},[326,1978,1953],{"class":1952},[326,1980,1014],{"class":1956},[326,1982,1960],{"class":1952},[326,1984,649],{"class":357},[326,1986,1966],{"class":1965},[326,1988,300],{"class":1969},[326,1990,1960],{"class":1965},[326,1992,562],{"class":357},[326,1994,1995,1997,2000,2002,2004,2006,2009,2011],{"class":328,"line":395},[326,1996,1953],{"class":1952},[326,1998,1999],{"class":1956},"created_at",[326,2001,1960],{"class":1952},[326,2003,649],{"class":357},[326,2005,1966],{"class":1965},[326,2007,2008],{"class":1969},"2026-10-27T14:03:11+00:00",[326,2010,1960],{"class":1965},[326,2012,562],{"class":357},[326,2014,2015,2017,2019,2021,2023,2026,2028,2030,2032,2034,2036,2039,2041,2043,2045,2047,2049,2051,2053,2055,2057],{"class":328,"line":402},[326,2016,1953],{"class":1952},[326,2018,1852],{"class":1956},[326,2020,1960],{"class":1952},[326,2022,649],{"class":357},[326,2024,2025],{"class":357}," {",[326,2027,1966],{"class":1952},[326,2029,544],{"class":416},[326,2031,1960],{"class":1952},[326,2033,649],{"class":357},[326,2035,1966],{"class":1965},[326,2037,2038],{"class":1969},"ord_123",[326,2040,1960],{"class":1965},[326,2042,505],{"class":357},[326,2044,1966],{"class":1952},[326,2046,1754],{"class":416},[326,2048,1960],{"class":1952},[326,2050,649],{"class":357},[326,2052,1966],{"class":1965},[326,2054,2008],{"class":1969},[326,2056,1960],{"class":1965},[326,2058,2059],{"class":357}," }\n",[326,2061,2062],{"class":328,"line":454},[326,2063,1790],{"class":357},[289,2065,2066,2067,920,2069,2071,2072,2074,2075,977,2078,2081],{},"The payload is built when the event is dispatched and stored on the delivery, so every retry sends the same ",[298,2068,1957],{},[298,2070,1852],{},". Use ",[298,2073,1957],{}," to deduplicate on the receiving end. To change the envelope, bind your own ",[298,2076,2077],{},"Glueful\\Api\\Webhooks\\Contracts\\WebhookPayloadInterface",[298,2079,2080],{},"build(string $event, array $data, array $metadata = []): array",").",[1311,2083,932],{"id":2084},"from-application-events",[289,2086,2087,2090,2091,2094,2095,2097,2098,920,2100,2102,2103,2105,2106,2109],{},[298,2088,2089],{},"DispatchesWebhooks"," (a trait) and ",[298,2092,2093],{},"#[Webhookable]"," (a class attribute) mark an ",[306,2096,1014],{"href":98}," as webhook-worthy. When ",[298,2099,923],{},[298,2101,919],{}," are on, the framework registers ",[298,2104,927],{}," at boot, so any marked event dispatched through ",[298,2107,2108],{},"EventService"," becomes a webhook dispatch. There's nothing to wire up:",[317,2111,2113],{"className":319,"code":2112,"language":321,"meta":322,"style":322},"use Glueful\\Api\\Webhooks\\Concerns\\DispatchesWebhooks;\nuse Glueful\\Events\\Contracts\\BaseEvent;\nuse Glueful\\Events\\EventService;\n\nfinal class OrderShippedEvent extends BaseEvent\n{\n    use DispatchesWebhooks; // webhook name derived from the class: \"order.shipped\"\n\n    public function __construct(public readonly string $orderId, public readonly string $carrier)\n    {\n        parent::__construct();\n    }\n\n    public function shouldDispatchWebhook(): bool\n    {\n        return $this->carrier !== 'internal';\n    }\n\n    public function webhookOptions(): array\n    {\n        return ['delay' => 10]; // same keys as dispatch() options: queue, connection, delay\n    }\n}\n\n// Anywhere: dispatching the event also queues the webhook deliveries\napp($context, EventService::class)->dispatch(new OrderShippedEvent($order->uuid, 'ups'));\n",[298,2114,2115,2140,2162,2178,2182,2198,2202,2215,2219,2257,2261,2273,2277,2281,2298,2302,2327,2331,2335,2352,2357,2381,2386,2391,2396,2402],{"__ignoreMap":322},[326,2116,2117,2119,2121,2123,2125,2127,2129,2131,2134,2136,2138],{"class":328,"line":329},[326,2118,333],{"class":332},[326,2120,337],{"class":336},[326,2122,341],{"class":340},[326,2124,344],{"class":336},[326,2126,341],{"class":340},[326,2128,125],{"class":336},[326,2130,341],{"class":340},[326,2132,2133],{"class":336},"Concerns",[326,2135,341],{"class":340},[326,2137,2089],{"class":353},[326,2139,358],{"class":357},[326,2141,2142,2144,2146,2148,2151,2153,2155,2157,2160],{"class":328,"line":361},[326,2143,333],{"class":332},[326,2145,337],{"class":336},[326,2147,341],{"class":340},[326,2149,2150],{"class":336},"Events",[326,2152,341],{"class":340},[326,2154,378],{"class":336},[326,2156,341],{"class":340},[326,2158,2159],{"class":353},"BaseEvent",[326,2161,358],{"class":357},[326,2163,2164,2166,2168,2170,2172,2174,2176],{"class":328,"line":388},[326,2165,333],{"class":332},[326,2167,337],{"class":336},[326,2169,341],{"class":340},[326,2171,2150],{"class":336},[326,2173,341],{"class":340},[326,2175,2108],{"class":353},[326,2177,358],{"class":357},[326,2179,2180],{"class":328,"line":395},[326,2181,392],{"emptyLinePlaceholder":391},[326,2183,2184,2186,2188,2191,2194],{"class":328,"line":402},[326,2185,1598],{"class":1597},[326,2187,1602],{"class":1601},[326,2189,2190],{"class":1605}," OrderShippedEvent",[326,2192,2193],{"class":1597}," extends",[326,2195,2197],{"class":2196},"sW3Pz"," BaseEvent\n",[326,2199,2200],{"class":328,"line":454},[326,2201,1611],{"class":357},[326,2203,2204,2207,2210,2212],{"class":328,"line":482},[326,2205,2206],{"class":332},"    use",[326,2208,2209],{"class":353}," DispatchesWebhooks",[326,2211,476],{"class":357},[326,2213,2214],{"class":398}," // webhook name derived from the class: \"order.shipped\"\n",[326,2216,2217],{"class":328,"line":487},[326,2218,392],{"emptyLinePlaceholder":391},[326,2220,2221,2223,2225,2227,2229,2232,2234,2237,2239,2242,2244,2247,2249,2251,2253,2255],{"class":328,"line":493},[326,2222,1616],{"class":1597},[326,2224,1619],{"class":1601},[326,2226,1623],{"class":1622},[326,2228,524],{"class":357},[326,2230,2231],{"class":1597},"public",[326,2233,1631],{"class":1597},[326,2235,2236],{"class":332}," string",[326,2238,464],{"class":357},[326,2240,2241],{"class":408},"orderId",[326,2243,505],{"class":357},[326,2245,2246],{"class":1597}," public",[326,2248,1631],{"class":1597},[326,2250,2236],{"class":332},[326,2252,464],{"class":357},[326,2254,570],{"class":408},[326,2256,1640],{"class":357},[326,2258,2259],{"class":328,"line":538},[326,2260,1645],{"class":357},[326,2262,2263,2266,2268,2271],{"class":328,"line":565},[326,2264,2265],{"class":1601},"        parent",[326,2267,420],{"class":412},[326,2269,2270],{"class":423},"__construct",[326,2272,1436],{"class":357},[326,2274,2275],{"class":328,"line":587},[326,2276,1650],{"class":357},[326,2278,2279],{"class":328,"line":1250},[326,2280,392],{"emptyLinePlaceholder":391},[326,2282,2283,2285,2287,2290,2293,2295],{"class":328,"line":1272},[326,2284,1616],{"class":1597},[326,2286,1619],{"class":1601},[326,2288,2289],{"class":423}," shouldDispatchWebhook",[326,2291,2292],{"class":357},"()",[326,2294,649],{"class":412},[326,2296,2297],{"class":332}," bool\n",[326,2299,2300],{"class":328,"line":1289},[326,2301,1645],{"class":357},[326,2303,2304,2308,2310,2312,2315,2318,2320,2323,2325],{"class":328,"line":1776},[326,2305,2307],{"class":2306},"sRxSC","        return",[326,2309,1706],{"class":1705},[326,2311,470],{"class":412},[326,2313,2314],{"class":408},"carrier ",[326,2316,2317],{"class":412},"!==",[326,2319,443],{"class":430},[326,2321,2322],{"class":434},"internal",[326,2324,431],{"class":430},[326,2326,358],{"class":357},[326,2328,2329],{"class":328,"line":1782},[326,2330,1650],{"class":357},[326,2332,2333],{"class":328,"line":1787},[326,2334,392],{"emptyLinePlaceholder":391},[326,2336,2338,2340,2342,2345,2347,2349],{"class":328,"line":2337},19,[326,2339,1616],{"class":1597},[326,2341,1619],{"class":1601},[326,2343,2344],{"class":423}," webhookOptions",[326,2346,2292],{"class":357},[326,2348,649],{"class":412},[326,2350,2351],{"class":332}," array\n",[326,2353,2355],{"class":328,"line":2354},20,[326,2356,1645],{"class":357},[326,2358,2360,2362,2364,2366,2368,2370,2372,2375,2378],{"class":328,"line":2359},21,[326,2361,2307],{"class":2306},[326,2363,1261],{"class":357},[326,2365,431],{"class":430},[326,2367,1907],{"class":434},[326,2369,431],{"class":430},[326,2371,549],{"class":412},[326,2373,2374],{"class":1479}," 10",[326,2376,2377],{"class":357},"];",[326,2379,2380],{"class":398}," // same keys as dispatch() options: queue, connection, delay\n",[326,2382,2384],{"class":328,"line":2383},22,[326,2385,1650],{"class":357},[326,2387,2389],{"class":328,"line":2388},23,[326,2390,1790],{"class":357},[326,2392,2394],{"class":328,"line":2393},24,[326,2395,392],{"emptyLinePlaceholder":391},[326,2397,2399],{"class":328,"line":2398},25,[326,2400,2401],{"class":398},"// Anywhere: dispatching the event also queues the webhook deliveries\n",[326,2403,2405,2407,2409,2411,2413,2416,2418,2420,2422,2424,2426,2428,2431,2433,2435,2437,2439,2441,2443,2445,2447,2449],{"class":328,"line":2404},26,[326,2406,496],{"class":423},[326,2408,499],{"class":357},[326,2410,502],{"class":408},[326,2412,505],{"class":357},[326,2414,2415],{"class":416}," EventService",[326,2417,420],{"class":412},[326,2419,513],{"class":332},[326,2421,516],{"class":357},[326,2423,470],{"class":412},[326,2425,521],{"class":423},[326,2427,524],{"class":357},[326,2429,2430],{"class":332},"new",[326,2432,2190],{"class":416},[326,2434,499],{"class":357},[326,2436,554],{"class":408},[326,2438,470],{"class":412},[326,2440,559],{"class":408},[326,2442,505],{"class":357},[326,2444,443],{"class":430},[326,2446,580],{"class":434},[326,2448,431],{"class":430},[326,2450,2451],{"class":357},"));\n",[289,2453,2454,2455,699,2457,2459,2460,699,2463,2466,2467,2470,2471,2474,2475,2477,2478,920,2481,2484],{},"With the trait, the payload is the event's own public properties (",[298,2456,2241],{},[298,2458,570],{},"). Override ",[298,2461,2462],{},"webhookEventName()",[298,2464,2465],{},"webhookPayload()"," or ",[298,2468,2469],{},"webhookOptions()"," to change the name, the data or the dispatch options. Without the trait, ",[298,2472,2473],{},"#[Webhookable(event: 'order.shipped', condition: 'shouldNotify')]"," sets the name and an optional condition method, and the payload is the event's public properties minus the ",[298,2476,2159],{}," ones. The attribute's ",[298,2479,2480],{},"only",[298,2482,2483],{},"except"," arguments are deprecated and have no effect.",[289,2486,2487,2488,2491],{},"The bridge listens at priority ",[298,2489,2490],{},"-100",", so your own listeners for the same event run first, and a listener that stops propagation also stops the webhook.",[1037,2493,2496],{"color":2494,"icon":2495,"variant":1041},"warning","i-lucide-triangle-alert",[289,2497,2498,2500,2501,2503,2504,2507],{},[293,2499,1046],{}," if you registered ",[298,2502,927],{}," yourself (for example with ",[298,2505,2506],{},"addListener(..., '@...WebhookEventListener:handleEvent')","), remove that registration, or each marked event dispatches its webhooks twice.",[312,2509,2511],{"id":2510},"delivery","Delivery",[289,2513,2514,2515,2517],{},"A queue worker runs ",[298,2516,1803],{}," for each delivery:",[2519,2520,2521,2528,2534,2537],"ol",{},[2522,2523,2524,2525,310],"li",{},"Loads the delivery and its subscription. A missing or inactive subscription marks the delivery ",[298,2526,2527],{},"failed",[2522,2529,2530,2531,2533],{},"Checks the destination (below). A refused URL marks it ",[298,2532,2527],{}," without sending.",[2522,2535,2536],{},"POSTs the JSON payload with redirects disabled.",[2522,2538,2539,2540,2543,2544,2547,2548,2551,2552,2555,2556,310],{},"A ",[298,2541,2542],{},"2xx"," response marks it ",[298,2545,2546],{},"delivered"," and fires ",[298,2549,2550],{},"WebhookDeliveredEvent",". Any other status, a ",[298,2553,2554],{},"3xx"," included, or a network error goes to ",[306,2557,2559],{"href":2558},"#retries","Retries",[289,2561,2562],{},"The response code and body are stored on the delivery.",[1311,2564,2566],{"id":2565},"request-headers","Request headers",[317,2568,2572],{"className":2569,"code":2570,"language":2571,"meta":322,"style":322},"language-http shiki shiki-themes material-theme-lighter github-light github-dark monokai","POST /hooks HTTP/1.1\nContent-Type: application/json\nUser-Agent: Glueful-Webhooks/1.0\nX-Webhook-ID: \u003Cdelivery uuid>\nX-Webhook-Event: order.shipped\nX-Webhook-Timestamp: 1793109791\nX-Webhook-Signature: t=1793109791,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd\n","http",[298,2573,2574,2579,2584,2589,2594,2599,2604],{"__ignoreMap":322},[326,2575,2576],{"class":328,"line":329},[326,2577,2578],{},"POST /hooks HTTP/1.1\n",[326,2580,2581],{"class":328,"line":361},[326,2582,2583],{},"Content-Type: application/json\n",[326,2585,2586],{"class":328,"line":388},[326,2587,2588],{},"User-Agent: Glueful-Webhooks/1.0\n",[326,2590,2591],{"class":328,"line":395},[326,2592,2593],{},"X-Webhook-ID: \u003Cdelivery uuid>\n",[326,2595,2596],{"class":328,"line":402},[326,2597,2598],{},"X-Webhook-Event: order.shipped\n",[326,2600,2601],{"class":328,"line":454},[326,2602,2603],{},"X-Webhook-Timestamp: 1793109791\n",[326,2605,2606],{"class":328,"line":482},[326,2607,2608],{},"X-Webhook-Signature: t=1793109791,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd\n",[1311,2610,2612],{"id":2611},"destination-checks","Destination checks",[289,2614,2615],{},"Before every send, the job refuses:",[2617,2618,2619,2633,2641],"ul",{},[2522,2620,2621,2622,2625,2626,2628,2629,2466,2631,516],{},"a scheme other than ",[298,2623,2624],{},"https"," when ",[298,2627,866],{}," is on (otherwise ",[298,2630,2571],{},[298,2632,2624],{},[2522,2634,2635,920,2638],{},[298,2636,2637],{},"localhost",[298,2639,2640],{},"*.localhost",[2522,2642,2643],{},"hosts that don't resolve, or that resolve to any private, reserved, loopback, link-local, unique-local or documentation address (IPv4 and IPv6, including IPv4-mapped IPv6)",[289,2645,2646,2647,2649],{},"The connection is pinned to the IP that passed the check, which closes the DNS-rebinding gap between checking and connecting. The management API also rejects a non-HTTPS URL when the subscription is created while ",[298,2648,866],{}," is on.",[312,2651,2653],{"id":2652},"signatures","Signatures",[289,2655,2656,2657,2659,2660,2662,2663,2665,2666,2668],{},"Each delivery is signed with the subscription's secret, Stripe-style, in the ",[298,2658,752],{}," header (default ",[298,2661,759],{},") with the ",[298,2664,769],{}," HMAC (default ",[298,2667,776],{},"):",[317,2670,2675],{"className":2671,"code":2673,"language":2674},[2672],"language-text","X-Webhook-Signature: t=\u003Cunix timestamp>,v1=\u003Chex HMAC of \"\u003Ctimestamp>.\u003Craw body>\">\nX-Webhook-Timestamp: \u003Cunix timestamp>\n","text",[298,2676,2673],{"__ignoreMap":322},[289,2678,2679,2680,2683,2684,2686],{},"This is the framework's only signing scheme: ",[298,2681,2682],{},"Glueful\\Http\\Services\\WebhookDeliveryService"," (ad-hoc deliveries with a ",[298,2685,473],{}," option) signs the same way.",[1037,2688,2689],{"color":1039,"icon":1040,"variant":1041},[289,2690,2691,2693,2694,2697,2698,2701,2702,2705,2706,2709],{},[293,2692,1046],{}," ",[298,2695,2696],{},"WebhookDeliveryService"," no longer sends ",[298,2699,2700],{},"X-Glueful-Signature: sha256=…"," / ",[298,2703,2704],{},"X-Glueful-Timestamp",". Receivers of its deliveries must verify the ",[298,2707,2708],{},"t=…,v1=…"," header above.",[289,2711,2712,2713,2716,2717,2720,2721,2723,2724,649],{},"A Glueful receiver can verify it with ",[298,2714,2715],{},"Webhook::verify()"," (or ",[298,2718,2719],{},"WebhookSignature::verify()","), which checks the HMAC in constant time and rejects timestamps more than 300 seconds old by default. ",[298,2722,2715],{}," uses the configured ",[298,2725,769],{},[317,2727,2729],{"className":319,"code":2728,"language":321,"meta":322,"style":322},"use Glueful\\Api\\Webhooks\\Webhook;\nuse Glueful\\Http\\Response;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\npublic function receive(Request $request): Response\n{\n    $valid = Webhook::verify(\n        $request->getContent(),                             // the raw body, not re-encoded JSON\n        (string) $request->headers->get('X-Webhook-Signature'),\n        $secret,                                            // the subscription's whsec_...\n        300,                                                // tolerance in seconds; null skips the age check\n    );\n\n    if (!$valid) {\n        return Response::error('Invalid signature', 401);\n    }\n    // ...\n}\n",[298,2730,2731,2751,2769,2793,2797,2822,2826,2845,2862,2896,2907,2917,2922,2926,2946,2974,2978,2983],{"__ignoreMap":322},[326,2732,2733,2735,2737,2739,2741,2743,2745,2747,2749],{"class":328,"line":329},[326,2734,333],{"class":332},[326,2736,337],{"class":336},[326,2738,341],{"class":340},[326,2740,344],{"class":336},[326,2742,341],{"class":340},[326,2744,125],{"class":336},[326,2746,341],{"class":340},[326,2748,354],{"class":353},[326,2750,358],{"class":357},[326,2752,2753,2755,2757,2759,2762,2764,2767],{"class":328,"line":361},[326,2754,333],{"class":332},[326,2756,337],{"class":336},[326,2758,341],{"class":340},[326,2760,2761],{"class":336},"Http",[326,2763,341],{"class":340},[326,2765,2766],{"class":353},"Response",[326,2768,358],{"class":357},[326,2770,2771,2773,2776,2778,2781,2783,2786,2788,2791],{"class":328,"line":388},[326,2772,333],{"class":332},[326,2774,2775],{"class":336}," Symfony",[326,2777,341],{"class":340},[326,2779,2780],{"class":336},"Component",[326,2782,341],{"class":340},[326,2784,2785],{"class":336},"HttpFoundation",[326,2787,341],{"class":340},[326,2789,2790],{"class":353},"Request",[326,2792,358],{"class":357},[326,2794,2795],{"class":328,"line":395},[326,2796,392],{"emptyLinePlaceholder":391},[326,2798,2799,2801,2803,2806,2808,2810,2812,2815,2817,2819],{"class":328,"line":402},[326,2800,2231],{"class":1597},[326,2802,1619],{"class":1601},[326,2804,2805],{"class":423}," receive",[326,2807,524],{"class":357},[326,2809,2790],{"class":416},[326,2811,464],{"class":357},[326,2813,2814],{"class":408},"request",[326,2816,516],{"class":357},[326,2818,649],{"class":412},[326,2820,2821],{"class":416}," Response\n",[326,2823,2824],{"class":328,"line":454},[326,2825,1611],{"class":357},[326,2827,2828,2831,2834,2836,2838,2840,2843],{"class":328,"line":482},[326,2829,2830],{"class":357},"    $",[326,2832,2833],{"class":408},"valid ",[326,2835,413],{"class":412},[326,2837,417],{"class":416},[326,2839,420],{"class":412},[326,2841,2842],{"class":423},"verify",[326,2844,1125],{"class":357},[326,2846,2847,2849,2851,2853,2856,2859],{"class":328,"line":487},[326,2848,1697],{"class":357},[326,2850,2814],{"class":408},[326,2852,470],{"class":412},[326,2854,2855],{"class":423},"getContent",[326,2857,2858],{"class":357},"(),",[326,2860,2861],{"class":398},"                             // the raw body, not re-encoded JSON\n",[326,2863,2864,2867,2870,2872,2874,2876,2878,2881,2883,2886,2888,2890,2892,2894],{"class":328,"line":493},[326,2865,2866],{"class":357},"        (",[326,2868,2869],{"class":1601},"string",[326,2871,516],{"class":357},[326,2873,464],{"class":357},[326,2875,2814],{"class":408},[326,2877,470],{"class":412},[326,2879,2880],{"class":408},"headers",[326,2882,470],{"class":412},[326,2884,2885],{"class":423},"get",[326,2887,524],{"class":357},[326,2889,431],{"class":430},[326,2891,759],{"class":434},[326,2893,431],{"class":430},[326,2895,1773],{"class":357},[326,2897,2898,2900,2902,2904],{"class":328,"line":538},[326,2899,1697],{"class":357},[326,2901,473],{"class":408},[326,2903,505],{"class":357},[326,2905,2906],{"class":398},"                                            // the subscription's whsec_...\n",[326,2908,2909,2912,2914],{"class":328,"line":565},[326,2910,2911],{"class":1479},"        300",[326,2913,505],{"class":357},[326,2915,2916],{"class":398},"                                                // tolerance in seconds; null skips the age check\n",[326,2918,2919],{"class":328,"line":587},[326,2920,2921],{"class":357},"    );\n",[326,2923,2924],{"class":328,"line":1250},[326,2925,392],{"emptyLinePlaceholder":391},[326,2927,2928,2931,2933,2936,2938,2941,2943],{"class":328,"line":1272},[326,2929,2930],{"class":2306},"    if",[326,2932,977],{"class":357},[326,2934,2935],{"class":412},"!",[326,2937,405],{"class":357},[326,2939,2940],{"class":408},"valid",[326,2942,516],{"class":357},[326,2944,2945],{"class":357}," {\n",[326,2947,2948,2950,2953,2955,2958,2960,2962,2965,2967,2969,2972],{"class":328,"line":1289},[326,2949,2307],{"class":2306},[326,2951,2952],{"class":416}," Response",[326,2954,420],{"class":412},[326,2956,2957],{"class":423},"error",[326,2959,524],{"class":357},[326,2961,431],{"class":430},[326,2963,2964],{"class":434},"Invalid signature",[326,2966,431],{"class":430},[326,2968,505],{"class":357},[326,2970,2971],{"class":1479}," 401",[326,2973,451],{"class":357},[326,2975,2976],{"class":328,"line":1776},[326,2977,1650],{"class":357},[326,2979,2980],{"class":328,"line":1782},[326,2981,2982],{"class":398},"    // ...\n",[326,2984,2985],{"class":328,"line":1787},[326,2986,1790],{"class":357},[289,2988,2989,2990,2993,2994,2997],{},"Receivers on other stacks compute ",[298,2991,2992],{},"hash_hmac('sha256', \"{$t}.{$rawBody}\", $secret)"," (or the configured algorithm) and compare it with each ",[298,2995,2996],{},"v*"," value in the header.",[312,2999,2559],{"id":3000},"retries",[289,3002,3003,3004,3006,3007,699,3010,3012,3013,3015],{},"When an attempt fails and fewer than ",[298,3005,829],{}," attempts have been made, the delivery becomes ",[298,3008,3009],{},"retrying",[298,3011,1035],{}," is set, and the job is released back to the queue with a delay taken from ",[298,3014,849],{},". The defaults:",[651,3017,3018,3028],{},[654,3019,3020],{},[657,3021,3022,3025],{},[660,3023,3024],{},"After attempt",[660,3026,3027],{},"Next try in",[673,3029,3030,3038,3046,3054,3062],{},[657,3031,3032,3035],{},[678,3033,3034],{},"1",[678,3036,3037],{},"1 minute",[657,3039,3040,3043],{},[678,3041,3042],{},"2",[678,3044,3045],{},"5 minutes",[657,3047,3048,3051],{},[678,3049,3050],{},"3",[678,3052,3053],{},"30 minutes",[657,3055,3056,3059],{},[678,3057,3058],{},"4",[678,3060,3061],{},"2 hours",[657,3063,3064,3067],{},[678,3065,3066],{},"5+",[678,3068,3069],{},"12 hours",[289,3071,3072,3073,2547,3075,3078,3079,699,3081,699,3083,920,3085,977,3087,2081],{},"With the default 5 attempts, the fifth failure marks the delivery ",[298,3074,2527],{},[298,3076,3077],{},"WebhookFailedEvent",". Delivery statuses are ",[298,3080,1799],{},[298,3082,3009],{},[298,3084,2546],{},[298,3086,2527],{},[298,3088,3089],{},"WebhookDelivery::STATUS_*",[289,3091,3092],{},"Retry failed or stuck deliveries by hand:",[317,3094,3096],{"className":319,"code":3095,"language":321,"meta":322,"style":322},"Webhook::retry('delivery-uuid');            // resets attempts to 0 and re-queues\nWebhook::failedDeliveries(50, date('Y-m-d H:i:s', strtotime('-1 day'))); // recent failures\n",[298,3097,3098,3121],{"__ignoreMap":322},[326,3099,3100,3102,3104,3107,3109,3111,3114,3116,3118],{"class":328,"line":329},[326,3101,354],{"class":416},[326,3103,420],{"class":412},[326,3105,3106],{"class":423},"retry",[326,3108,524],{"class":357},[326,3110,431],{"class":430},[326,3112,3113],{"class":434},"delivery-uuid",[326,3115,431],{"class":430},[326,3117,1482],{"class":357},[326,3119,3120],{"class":398},"            // resets attempts to 0 and re-queues\n",[326,3122,3123,3125,3127,3130,3132,3135,3137,3139,3141,3143,3146,3148,3150,3153,3155,3157,3160,3162,3165],{"class":328,"line":361},[326,3124,354],{"class":416},[326,3126,420],{"class":412},[326,3128,3129],{"class":423},"failedDeliveries",[326,3131,524],{"class":357},[326,3133,3134],{"class":1479},"50",[326,3136,505],{"class":357},[326,3138,1761],{"class":1622},[326,3140,524],{"class":357},[326,3142,431],{"class":430},[326,3144,3145],{"class":434},"Y-m-d H:i:s",[326,3147,431],{"class":430},[326,3149,505],{"class":357},[326,3151,3152],{"class":1622}," strtotime",[326,3154,524],{"class":357},[326,3156,431],{"class":430},[326,3158,3159],{"class":434},"-1 day",[326,3161,431],{"class":430},[326,3163,3164],{"class":357},")));",[326,3166,3167],{"class":398}," // recent failures\n",[312,3169,3171],{"id":3170},"monitoring-events","Monitoring Events",[651,3173,3174,3187],{},[654,3175,3176],{},[657,3177,3178,3181,3184],{},[660,3179,3180],{},"Event",[660,3182,3183],{},"Fired when",[660,3185,3186],{},"Useful accessors",[673,3188,3189,3209,3235],{},[657,3190,3191,3196,3201],{},[678,3192,3193],{},[298,3194,3195],{},"Glueful\\Api\\Webhooks\\Events\\WebhookDispatchedEvent",[678,3197,3198,3200],{},[298,3199,702],{}," created at least one delivery",[678,3202,3203,699,3206],{},[298,3204,3205],{},"getEvent()",[298,3207,3208],{},"getDeliveryCount()",[657,3210,3211,3216,3221],{},[678,3212,3213],{},[298,3214,3215],{},"Glueful\\Events\\Webhook\\WebhookDeliveredEvent",[678,3217,3218,3219],{},"An endpoint answered ",[298,3220,2542],{},[678,3222,3223,699,3226,699,3229,699,3232],{},[298,3224,3225],{},"getUrl()",[298,3227,3228],{},"getStatusCode()",[298,3230,3231],{},"getDurationMs()",[298,3233,3234],{},"getHost()",[657,3236,3237,3242,3245],{},[678,3238,3239],{},[298,3240,3241],{},"Glueful\\Events\\Webhook\\WebhookFailedEvent",[678,3243,3244],{},"Attempts are exhausted, or the URL was refused",[678,3246,3247,699,3250,699,3252,699,3255,699,3258],{},[298,3248,3249],{},"getReason()",[298,3251,3228],{},[298,3253,3254],{},"isNetworkError()",[298,3256,3257],{},"isClientError()",[298,3259,3260],{},"isServerError()",[317,3262,3264],{"className":319,"code":3263,"language":321,"meta":322,"style":322},"app($context, EventService::class)->addListener(\n    WebhookFailedEvent::class,\n    fn (WebhookFailedEvent $e) => $logger->warning('Webhook failed', [\n        'host' => $e->getHost(),\n        'reason' => $e->getReason(),\n    ]),\n);\n",[298,3265,3266,3291,3302,3342,3366,3388,3393],{"__ignoreMap":322},[326,3267,3268,3270,3272,3274,3276,3278,3280,3282,3284,3286,3289],{"class":328,"line":329},[326,3269,496],{"class":423},[326,3271,499],{"class":357},[326,3273,502],{"class":408},[326,3275,505],{"class":357},[326,3277,2415],{"class":416},[326,3279,420],{"class":412},[326,3281,513],{"class":332},[326,3283,516],{"class":357},[326,3285,470],{"class":412},[326,3287,3288],{"class":423},"addListener",[326,3290,1125],{"class":357},[326,3292,3293,3296,3298,3300],{"class":328,"line":361},[326,3294,3295],{"class":416},"    WebhookFailedEvent",[326,3297,420],{"class":412},[326,3299,513],{"class":332},[326,3301,562],{"class":357},[326,3303,3304,3307,3309,3311,3313,3316,3318,3320,3322,3325,3327,3329,3331,3333,3336,3338,3340],{"class":328,"line":388},[326,3305,3306],{"class":1601},"    fn",[326,3308,977],{"class":357},[326,3310,3077],{"class":416},[326,3312,464],{"class":357},[326,3314,3315],{"class":408},"e",[326,3317,516],{"class":357},[326,3319,549],{"class":357},[326,3321,464],{"class":357},[326,3323,3324],{"class":408},"logger",[326,3326,470],{"class":412},[326,3328,2494],{"class":423},[326,3330,524],{"class":357},[326,3332,431],{"class":430},[326,3334,3335],{"class":434},"Webhook failed",[326,3337,431],{"class":430},[326,3339,505],{"class":357},[326,3341,535],{"class":357},[326,3343,3344,3347,3350,3352,3354,3356,3358,3360,3363],{"class":328,"line":395},[326,3345,3346],{"class":430},"        '",[326,3348,3349],{"class":434},"host",[326,3351,431],{"class":430},[326,3353,549],{"class":412},[326,3355,464],{"class":357},[326,3357,3315],{"class":408},[326,3359,470],{"class":412},[326,3361,3362],{"class":423},"getHost",[326,3364,3365],{"class":357},"(),\n",[326,3367,3368,3370,3373,3375,3377,3379,3381,3383,3386],{"class":328,"line":402},[326,3369,3346],{"class":430},[326,3371,3372],{"class":434},"reason",[326,3374,431],{"class":430},[326,3376,549],{"class":412},[326,3378,464],{"class":357},[326,3380,3315],{"class":408},[326,3382,470],{"class":412},[326,3384,3385],{"class":423},"getReason",[326,3387,3365],{"class":357},[326,3389,3390],{"class":328,"line":454},[326,3391,3392],{"class":357},"    ]),\n",[326,3394,3395],{"class":328,"line":482},[326,3396,451],{"class":357},[312,3398,3400],{"id":3399},"management-api","Management API",[289,3402,3403,3404,3407,3408,3411,3412,3415,3416,3419,3420,3423,3424,649],{},"Core ships ",[298,3405,3406],{},"routes/webhooks.php",", which mounts ",[298,3409,3410],{},"WebhookController"," under the API prefix (for example ",[298,3413,3414],{},"/api/v1/webhooks/...","). Every route requires ",[298,3417,3418],{},"auth","; reads require the ",[298,3421,3422],{},"system.config.view"," permission and changes require ",[298,3425,3426],{},"system.config.edit",[651,3428,3429,3442],{},[654,3430,3431],{},[657,3432,3433,3436,3439],{},[660,3434,3435],{},"Method",[660,3437,3438],{},"Path",[660,3440,3441],{},"Permission",[673,3443,3444,3460,3475,3490,3505,3520,3535,3550,3565,3580,3595],{},[657,3445,3446,3451,3456],{},[678,3447,3448],{},[298,3449,3450],{},"GET",[678,3452,3453],{},[298,3454,3455],{},"/webhooks/subscriptions",[678,3457,3458],{},[298,3459,3422],{},[657,3461,3462,3467,3471],{},[678,3463,3464],{},[298,3465,3466],{},"POST",[678,3468,3469],{},[298,3470,3455],{},[678,3472,3473],{},[298,3474,3426],{},[657,3476,3477,3481,3486],{},[678,3478,3479],{},[298,3480,3450],{},[678,3482,3483],{},[298,3484,3485],{},"/webhooks/subscriptions/{id}",[678,3487,3488],{},[298,3489,3422],{},[657,3491,3492,3497,3501],{},[678,3493,3494],{},[298,3495,3496],{},"PATCH",[678,3498,3499],{},[298,3500,3485],{},[678,3502,3503],{},[298,3504,3426],{},[657,3506,3507,3512,3516],{},[678,3508,3509],{},[298,3510,3511],{},"DELETE",[678,3513,3514],{},[298,3515,3485],{},[678,3517,3518],{},[298,3519,3426],{},[657,3521,3522,3526,3531],{},[678,3523,3524],{},[298,3525,3466],{},[678,3527,3528],{},[298,3529,3530],{},"/webhooks/subscriptions/{id}/rotate-secret",[678,3532,3533],{},[298,3534,3426],{},[657,3536,3537,3541,3546],{},[678,3538,3539],{},[298,3540,3466],{},[678,3542,3543],{},[298,3544,3545],{},"/webhooks/subscriptions/{id}/test",[678,3547,3548],{},[298,3549,3426],{},[657,3551,3552,3556,3561],{},[678,3553,3554],{},[298,3555,3450],{},[678,3557,3558],{},[298,3559,3560],{},"/webhooks/subscriptions/{id}/stats",[678,3562,3563],{},[298,3564,3422],{},[657,3566,3567,3571,3576],{},[678,3568,3569],{},[298,3570,3450],{},[678,3572,3573],{},[298,3574,3575],{},"/webhooks/deliveries",[678,3577,3578],{},[298,3579,3422],{},[657,3581,3582,3586,3591],{},[678,3583,3584],{},[298,3585,3450],{},[678,3587,3588],{},[298,3589,3590],{},"/webhooks/deliveries/{id}",[678,3592,3593],{},[298,3594,3422],{},[657,3596,3597,3601,3606],{},[678,3598,3599],{},[298,3600,3466],{},[678,3602,3603],{},[298,3604,3605],{},"/webhooks/deliveries/{id}/retry",[678,3607,3608],{},[298,3609,3426],{},[289,3611,3612,3615,3616,3618,3619,3622,3623,3626,3627,3630,3631,3634,3635,3638,3639,3642],{},[298,3613,3614],{},"{id}"," is the subscription or delivery ",[293,3617,559],{},". Creating a subscription takes ",[298,3620,3621],{},"{\"url\": \"...\", \"events\": [\"order.*\"], \"metadata\": {}}"," and returns ",[298,3624,3625],{},"201"," with the secret. That response and ",[298,3628,3629],{},"rotate-secret"," are the only ones that include it. The routes are rate limited, and the actions carry ",[298,3632,3633],{},"#[ApiOperation]","/",[298,3636,3637],{},"#[ApiResponse]"," attributes, so they show up in the generated ",[306,3640,3641],{"href":150},"OpenAPI"," spec.",[1037,3644,3645],{"color":2494,"icon":2495,"variant":1041},[289,3646,3647,3649,3650,3652],{},[293,3648,1046],{}," if your app mounted ",[298,3651,3410],{}," on these paths itself, remove those routes.",[312,3654,3656],{"id":3655},"cli","CLI",[317,3658,3660],{"className":593,"code":3659,"language":595,"meta":322,"style":322},"php glueful webhook:list --active --stats       # alias: webhooks; also --inactive, --json\nphp glueful webhook:test https://example.com/hooks --secret=whsec_... --event=order.shipped\nphp glueful webhook:test https://example.com/hooks --subscription=wh_sub_abc123\nphp glueful webhook:retry \u003Cdelivery-uuid>\nphp glueful webhook:retry --failed --since=\"1 hour ago\" --limit=50 --dry-run\nphp glueful webhook:cleanup\n",[298,3661,3662,3680,3698,3711,3732,3759],{"__ignoreMap":322},[326,3663,3664,3666,3668,3671,3674,3677],{"class":328,"line":329},[326,3665,321],{"class":607},[326,3667,610],{"class":434},[326,3669,3670],{"class":434}," webhook:list",[326,3672,3673],{"class":634}," --active",[326,3675,3676],{"class":634}," --stats",[326,3678,3679],{"class":398},"       # alias: webhooks; also --inactive, --json\n",[326,3681,3682,3684,3686,3689,3692,3695],{"class":328,"line":361},[326,3683,321],{"class":607},[326,3685,610],{"class":434},[326,3687,3688],{"class":434}," webhook:test",[326,3690,3691],{"class":434}," https://example.com/hooks",[326,3693,3694],{"class":634}," --secret=whsec_...",[326,3696,3697],{"class":634}," --event=order.shipped\n",[326,3699,3700,3702,3704,3706,3708],{"class":328,"line":388},[326,3701,321],{"class":607},[326,3703,610],{"class":434},[326,3705,3688],{"class":434},[326,3707,3691],{"class":434},[326,3709,3710],{"class":634}," --subscription=wh_sub_abc123\n",[326,3712,3713,3715,3717,3720,3723,3726,3729],{"class":328,"line":395},[326,3714,321],{"class":607},[326,3716,610],{"class":434},[326,3718,3719],{"class":434}," webhook:retry",[326,3721,3722],{"class":412}," \u003C",[326,3724,3725],{"class":434},"delivery-uui",[326,3727,3728],{"class":408},"d",[326,3730,3731],{"class":412},">\n",[326,3733,3734,3736,3738,3740,3743,3746,3748,3751,3753,3756],{"class":328,"line":402},[326,3735,321],{"class":607},[326,3737,610],{"class":434},[326,3739,3719],{"class":434},[326,3741,3742],{"class":634}," --failed",[326,3744,3745],{"class":634}," --since=",[326,3747,1960],{"class":430},[326,3749,3750],{"class":434},"1 hour ago",[326,3752,1960],{"class":430},[326,3754,3755],{"class":634}," --limit=50",[326,3757,3758],{"class":634}," --dry-run\n",[326,3760,3761,3763,3765],{"class":328,"line":454},[326,3762,321],{"class":607},[326,3764,610],{"class":434},[326,3766,3767],{"class":434}," webhook:cleanup\n",[289,3769,3770,3773,3774,699,3776,699,3779,3782],{},[298,3771,3772],{},"webhook:test"," (and the API's test action) sends synchronously, passes through the same destination checks, and signs when a secret is available. Its body is a fixed test payload (",[298,3775,1014],{},[298,3777,3778],{},"timestamp",[298,3780,3781],{},"data.test: true","), not the delivery envelope.",[312,3784,3786],{"id":3785},"retention","Retention",[289,3788,3789,3792,3793,3795,3796,3798,3799,3801,3802,3795,3804,3798,3806,3808],{},[298,3790,3791],{},"Webhook::cleanup()"," deletes ",[298,3794,2546],{}," records older than ",[298,3797,885],{}," (by ",[298,3800,1032],{},") and ",[298,3803,2527],{},[298,3805,902],{},[298,3807,1999],{},"). Pending and retrying deliveries are never removed by age.",[289,3810,3811,3812,3815,3816,3819,3820,3823,3824,310],{},"The framework schedule runs it every day at 03:30 as the ",[298,3813,3814],{},"webhook_cleanup"," job (",[298,3817,3818],{},"WebhookCleanupJob",", disable with ",[298,3821,3822],{},"WEBHOOK_CLEANUP_ENABLED=false","). Run it by hand with ",[298,3825,3826],{},"php glueful webhook:cleanup",[1037,3828,3829],{"color":2494,"icon":2495,"variant":1041},[289,3830,3831,3832,3835,3836,3839,3840,3843,3844,3846,3847,310],{},"If your app has its own ",[298,3833,3834],{},"config/schedule.php"," with a ",[298,3837,3838],{},"jobs"," list, that list ",[293,3841,3842],{},"replaces"," the framework's. Include ",[298,3845,3814],{}," in it, or delivery records are never purged. See ",[306,3848,3849],{"href":122},"Scheduling",[312,3851,309],{"id":3852},"receiving-webhooks",[289,3854,3855,3856,3858,3859,3862,3863,3866,3867,3870,3871,3874,3875,3878,3879,3882,3883,310],{},"Nothing in core receives or verifies third-party webhooks. Define a route for the provider and verify its signature with the provider's scheme. Leave ",[298,3857,3418],{}," off the route (the signature is the credential) and keep it out of the ",[298,3860,3861],{},"csrf"," middleware: mount it under ",[298,3864,3865],{},"/webhooks/inbound/\u003Cprovider>",", which the default ",[298,3868,3869],{},"exemptRoutes"," entry ",[298,3872,3873],{},"webhooks/inbound/*"," covers. (Since 1.91 the shipped entry is no longer ",[298,3876,3877],{},"webhooks/*",", which would also have exempted the management API.) For handlers whose failures must surface so the sender retries, dispatch your domain event with ",[298,3880,3881],{},"EventService::dispatchOrFail()",". See ",[306,3884,2150],{"href":98},[312,3886,3888],{"id":3887},"next-steps","Next Steps",[2617,3890,3891,3899,3904],{},[2522,3892,3893,3895,3896,3898],{},[306,3894,113],{"href":114}," - Run the ",[298,3897,644],{}," queue worker",[2522,3900,3901,3903],{},[306,3902,97],{"href":98}," - Listen for delivery outcomes",[2522,3905,3906,3908,3909,3911],{},[306,3907,3849],{"href":122}," - The ",[298,3910,3814],{}," job",[3913,3914,3915],"style",{},"html pre.shiki code .shWJe, html code.shiki .shWJe{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sv8o3, html code.shiki .sv8o3{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .seZir, html code.shiki .seZir{--shiki-light:#90A4AE;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sFhLe, html code.shiki .sFhLe{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sMTiH, html code.shiki .sMTiH{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .sMLJd, html code.shiki .sMLJd{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#66D9EF}html pre.shiki code .sSBr1, html code.shiki .sSBr1{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#FD971F}html pre.shiki code .saDeg, html code.shiki .saDeg{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sEff5, html code.shiki .sEff5{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sh1VR, html code.shiki .sh1VR{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sINAO, html code.shiki .sINAO{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sW3Pz, html code.shiki .sW3Pz{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-font-style:inherit;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-font-style:inherit;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-font-style:italic;--shiki-sepia-text-decoration:underline}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}",{"title":322,"searchDepth":329,"depth":361,"links":3917},[3918,3919,3920,3921,3925,3929,3933,3934,3935,3936,3937,3938,3939,3940],{"id":314,"depth":361,"text":315},{"id":638,"depth":361,"text":137},{"id":935,"depth":361,"text":204},{"id":1053,"depth":361,"text":1054,"children":3922},[3923,3924],{"id":1313,"depth":388,"text":1314},{"id":1368,"depth":388,"text":1369},{"id":1548,"depth":361,"text":1549,"children":3926},[3927,3928],{"id":1017,"depth":388,"text":1933},{"id":2084,"depth":388,"text":932},{"id":2510,"depth":361,"text":2511,"children":3930},[3931,3932],{"id":2565,"depth":388,"text":2566},{"id":2611,"depth":388,"text":2612},{"id":2652,"depth":361,"text":2653},{"id":3000,"depth":361,"text":2559},{"id":3170,"depth":361,"text":3171},{"id":3399,"depth":361,"text":3400},{"id":3655,"depth":361,"text":3656},{"id":3785,"depth":361,"text":3786},{"id":3852,"depth":361,"text":309},{"id":3887,"depth":361,"text":3888},"Signed outbound webhooks with subscriptions, queued delivery, retries and retention","md",null,{},{"title":125,"description":3941},"NUwK8LvW-rD9o3p43Hw0cZzLqbwtGLngkyerNZatJTo",[3948,3950],{"title":121,"path":122,"stem":123,"description":3949,"children":-1},"Run tasks on a schedule with cron-like syntax",{"title":134,"path":130,"stem":135,"description":3951,"children":-1},"Advanced techniques and patterns for production applications",1793709789166]