[{"data":1,"prerenderedAt":2334},["ShallowReactive",2],{"navigation_docs":3,"-guides-webhooks":132,"-guides-webhooks-surround":2329},[4,30,47,128],{"title":5,"path":6,"stem":7,"children":8,"page":29},"Getting Started","\u002Fgetting-started","1.getting-started",[9,13,17,21,25],{"title":10,"path":11,"stem":12},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction",{"title":14,"path":15,"stem":16},"Quickstart","\u002Fgetting-started\u002Fquickstart","1.getting-started\u002F2.quickstart",{"title":18,"path":19,"stem":20},"Authentication","\u002Fgetting-started\u002Fauthentication","1.getting-started\u002F3.authentication",{"title":22,"path":23,"stem":24},"Self-Hosting","\u002Fgetting-started\u002Fself-hosting","1.getting-started\u002F4.self-hosting",{"title":26,"path":27,"stem":28},"Build an Integration","\u002Fgetting-started\u002Fbuild-an-integration","1.getting-started\u002F5.build-an-integration",false,{"title":31,"path":32,"stem":33,"children":34,"page":29},"Sdks","\u002Fsdks","2.sdks",[35,39,43],{"title":36,"path":37,"stem":38},"TypeScript SDK","\u002Fsdks\u002Ftypescript","2.sdks\u002F1.typescript",{"title":40,"path":41,"stem":42},"Python SDK","\u002Fsdks\u002Fpython","2.sdks\u002F2.python",{"title":44,"path":45,"stem":46},"PHP SDK","\u002Fsdks\u002Fphp","2.sdks\u002F3.php",{"title":48,"path":49,"stem":50,"children":51,"page":29},"Guides","\u002Fguides","3.guides",[52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124],{"title":53,"path":54,"stem":55},"Customers","\u002Fguides\u002Fcustomers","3.guides\u002F1.customers",{"title":57,"path":58,"stem":59},"Usage-Based Billing","\u002Fguides\u002Fusage-billing","3.guides\u002F10.usage-billing",{"title":61,"path":62,"stem":63},"Wallets","\u002Fguides\u002Fwallets","3.guides\u002F11.wallets",{"title":65,"path":66,"stem":67},"Coupons, Add-ons and Credit Notes","\u002Fguides\u002Fcoupons-add-ons-credit-notes","3.guides\u002F12.coupons-add-ons-credit-notes",{"title":69,"path":70,"stem":71},"Taxes","\u002Fguides\u002Ftaxes","3.guides\u002F13.taxes",{"title":73,"path":74,"stem":75},"Customer Portal","\u002Fguides\u002Fcustomer-portal","3.guides\u002F14.customer-portal",{"title":77,"path":78,"stem":79},"Ledger","\u002Fguides\u002Fledger","3.guides\u002F15.ledger",{"title":81,"path":82,"stem":83},"Plan Limits and Access","\u002Fguides\u002Fplan-limits-and-access","3.guides\u002F16.plan-limits-and-access",{"title":85,"path":86,"stem":87},"Currencies and Exchange Rates","\u002Fguides\u002Fcurrencies","3.guides\u002F17.currencies",{"title":89,"path":90,"stem":91},"Charge a Customer","\u002Fguides\u002Fcharge-a-customer","3.guides\u002F18.charge-a-customer",{"title":93,"path":94,"stem":95},"One-time Products & Partial Payments","\u002Fguides\u002Fone-time-products-and-partial-payments","3.guides\u002F19.one-time-products-and-partial-payments",{"title":97,"path":98,"stem":99},"Plans & Pricing","\u002Fguides\u002Fplans-and-pricing","3.guides\u002F2.plans-and-pricing",{"title":101,"path":102,"stem":103},"Subscriptions","\u002Fguides\u002Fsubscriptions","3.guides\u002F3.subscriptions",{"title":105,"path":106,"stem":107},"Invoices & Payments","\u002Fguides\u002Finvoices-and-payments","3.guides\u002F4.invoices-and-payments",{"title":109,"path":110,"stem":111},"Payment Providers","\u002Fguides\u002Fpayment-providers","3.guides\u002F5.payment-providers",{"title":113,"path":114,"stem":115},"Analytics","\u002Fguides\u002Fanalytics","3.guides\u002F6.analytics",{"title":117,"path":118,"stem":119},"Webhooks","\u002Fguides\u002Fwebhooks","3.guides\u002F7.webhooks",{"title":121,"path":122,"stem":123},"Sandbox","\u002Fguides\u002Fsandbox","3.guides\u002F8.sandbox",{"title":125,"path":126,"stem":127},"Money Movement","\u002Fguides\u002Fmoney-movement","3.guides\u002F9.money-movement",{"title":129,"path":130,"stem":131},"API Reference","\u002Fapi-reference","4.api-reference",{"id":133,"title":117,"body":134,"description":2323,"extension":2324,"links":2325,"meta":2326,"navigation":275,"path":118,"seo":2327,"stem":119,"__hash__":2328},"docs\u002F3.guides\u002F7.webhooks.md",{"type":135,"value":136,"toc":2315},"minimark",[137,141,146,149,470,492,500,504,511,688,693,696,761,767,771,1055,1059,1073,1901,1904,1980,1987,1991,1994,2204,2208,2211,2305,2311],[138,139,140],"p",{},"PimaBill sends webhook notifications to your application when events occur: payments, subscription changes, collections, payouts and more. Each organisation has one webhook URL.",[142,143,145],"h2",{"id":144},"setup","Setup",[138,147,148],{},"Set your webhook URL in the dashboard, or through the API:",[150,151,153,234,425],"code-group",{"sync":152},"lang",[154,155,161],"pre",{"className":156,"code":157,"filename":158,"language":159,"meta":160,"style":160},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -X PATCH http:\u002F\u002Flocalhost:4000\u002Fapi\u002Ftenants\u002Fme \\\n  -H \"Authorization: Bearer sk_live_a1b2c3d4...\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"webhookUrl\": \"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Fpimabill\"}'\n","cURL","bash","",[162,163,164,187,205,219],"code",{"__ignoreMap":160},[165,166,169,173,177,180,183],"span",{"class":167,"line":168},"line",1,[165,170,172],{"class":171},"sBMFI","curl",[165,174,176],{"class":175},"sfazB"," -X",[165,178,179],{"class":175}," PATCH",[165,181,182],{"class":175}," http:\u002F\u002Flocalhost:4000\u002Fapi\u002Ftenants\u002Fme",[165,184,186],{"class":185},"sTEyZ"," \\\n",[165,188,190,193,197,200,203],{"class":167,"line":189},2,[165,191,192],{"class":175},"  -H",[165,194,196],{"class":195},"sMK4o"," \"",[165,198,199],{"class":175},"Authorization: Bearer sk_live_a1b2c3d4...",[165,201,202],{"class":195},"\"",[165,204,186],{"class":185},[165,206,208,210,212,215,217],{"class":167,"line":207},3,[165,209,192],{"class":175},[165,211,196],{"class":195},[165,213,214],{"class":175},"Content-Type: application\u002Fjson",[165,216,202],{"class":195},[165,218,186],{"class":185},[165,220,222,225,228,231],{"class":167,"line":221},4,[165,223,224],{"class":175},"  -d",[165,226,227],{"class":195}," '",[165,229,230],{"class":175},"{\"webhookUrl\": \"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Fpimabill\"}",[165,232,233],{"class":195},"'\n",[154,235,240],{"className":236,"code":237,"filename":238,"language":239,"meta":160,"style":160},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { PimaBillClient } from 'pimabill';\n\nconst client = new PimaBillClient({ token: 'sk_live_a1b2c3d4...' });\n\nawait client.tenants.updateMe({\n  webhookUrl: 'https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Fpimabill',\n});\n\n\u002F\u002F The secret your endpoint uses to check signatures:\nconst { webhookSecret } = await client.tenants.getMe();\n","TypeScript","typescript",[162,241,242,271,277,322,326,351,369,379,384,391],{"__ignoreMap":160},[165,243,244,248,251,254,257,260,262,265,268],{"class":167,"line":168},[165,245,247],{"class":246},"s7zQu","import",[165,249,250],{"class":195}," {",[165,252,253],{"class":185}," PimaBillClient",[165,255,256],{"class":195}," }",[165,258,259],{"class":246}," from",[165,261,227],{"class":195},[165,263,264],{"class":175},"pimabill",[165,266,267],{"class":195},"'",[165,269,270],{"class":195},";\n",[165,272,273],{"class":167,"line":189},[165,274,276],{"emptyLinePlaceholder":275},true,"\n",[165,278,279,283,286,289,292,295,298,301,305,308,310,313,315,317,320],{"class":167,"line":207},[165,280,282],{"class":281},"spNyl","const",[165,284,285],{"class":185}," client ",[165,287,288],{"class":195},"=",[165,290,291],{"class":195}," new",[165,293,253],{"class":294},"s2Zo4",[165,296,297],{"class":185},"(",[165,299,300],{"class":195},"{",[165,302,304],{"class":303},"swJcz"," token",[165,306,307],{"class":195},":",[165,309,227],{"class":195},[165,311,312],{"class":175},"sk_live_a1b2c3d4...",[165,314,267],{"class":195},[165,316,256],{"class":195},[165,318,319],{"class":185},")",[165,321,270],{"class":195},[165,323,324],{"class":167,"line":221},[165,325,276],{"emptyLinePlaceholder":275},[165,327,329,332,335,338,341,343,346,348],{"class":167,"line":328},5,[165,330,331],{"class":246},"await",[165,333,334],{"class":185}," client",[165,336,337],{"class":195},".",[165,339,340],{"class":185},"tenants",[165,342,337],{"class":195},[165,344,345],{"class":294},"updateMe",[165,347,297],{"class":185},[165,349,350],{"class":195},"{\n",[165,352,354,357,359,361,364,366],{"class":167,"line":353},6,[165,355,356],{"class":303},"  webhookUrl",[165,358,307],{"class":195},[165,360,227],{"class":195},[165,362,363],{"class":175},"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Fpimabill",[165,365,267],{"class":195},[165,367,368],{"class":195},",\n",[165,370,372,375,377],{"class":167,"line":371},7,[165,373,374],{"class":195},"}",[165,376,319],{"class":185},[165,378,270],{"class":195},[165,380,382],{"class":167,"line":381},8,[165,383,276],{"emptyLinePlaceholder":275},[165,385,387],{"class":167,"line":386},9,[165,388,390],{"class":389},"sHwdD","\u002F\u002F The secret your endpoint uses to check signatures:\n",[165,392,394,396,398,401,403,406,409,411,413,415,417,420,423],{"class":167,"line":393},10,[165,395,282],{"class":281},[165,397,250],{"class":195},[165,399,400],{"class":185}," webhookSecret ",[165,402,374],{"class":195},[165,404,405],{"class":195}," =",[165,407,408],{"class":246}," await",[165,410,334],{"class":185},[165,412,337],{"class":195},[165,414,340],{"class":185},[165,416,337],{"class":195},[165,418,419],{"class":294},"getMe",[165,421,422],{"class":185},"()",[165,424,270],{"class":195},[154,426,431],{"className":427,"code":428,"filename":429,"language":430,"meta":160,"style":160},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from pimabill import PimaBillClient\n\nclient = PimaBillClient(token=\"sk_live_a1b2c3d4...\")\n\nclient.tenants.update_me(webhook_url=\"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Fpimabill\")\n\n# The secret your endpoint uses to check signatures:\nwebhook_secret = client.tenants.get_me().webhook_secret\n","Python","python",[162,432,433,438,442,447,451,456,460,465],{"__ignoreMap":160},[165,434,435],{"class":167,"line":168},[165,436,437],{},"from pimabill import PimaBillClient\n",[165,439,440],{"class":167,"line":189},[165,441,276],{"emptyLinePlaceholder":275},[165,443,444],{"class":167,"line":207},[165,445,446],{},"client = PimaBillClient(token=\"sk_live_a1b2c3d4...\")\n",[165,448,449],{"class":167,"line":221},[165,450,276],{"emptyLinePlaceholder":275},[165,452,453],{"class":167,"line":328},[165,454,455],{},"client.tenants.update_me(webhook_url=\"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Fpimabill\")\n",[165,457,458],{"class":167,"line":353},[165,459,276],{"emptyLinePlaceholder":275},[165,461,462],{"class":167,"line":371},[165,463,464],{},"# The secret your endpoint uses to check signatures:\n",[165,466,467],{"class":167,"line":381},[165,468,469],{},"webhook_secret = client.tenants.get_me().webhook_secret\n",[138,471,472,473,476,477,480,481,484,485,488,489,337],{},"Your app (or its deployment script) can do this itself with an API key. Setting the URL needs the ",[162,474,475],{},"write"," or ",[162,478,479],{},"tenants:write"," scope. ",[162,482,483],{},"GET \u002Fapi\u002Ftenants\u002Fme"," returns ",[162,486,487],{},"webhookSecret"," only to a key with one of those scopes, or to a person with the Developer role or above. Rotate the secret with ",[162,490,491],{},"POST \u002Fapi\u002Ftenants\u002Fme\u002Fwebhook-secret\u002Frotate",[138,493,494,495,499],{},"Live and the ",[496,497,498],"a",{"href":122},"sandbox"," each have their own webhook URL and secret. Set them separately, each with that environment's key.",[142,501,503],{"id":502},"event-payload","Event Payload",[138,505,506,507,510],{},"Every event is a ",[162,508,509],{},"POST"," with a JSON body:",[154,512,516],{"className":513,"code":514,"language":515,"meta":160,"style":160},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"event\": \"payment.succeeded\",\n  \"timestamp\": \"2025-01-15T10:30:00.000Z\",\n  \"data\": {\n    \"id\": \"pay_abc123\",\n    \"invoiceId\": \"inv_xyz789\",\n    \"amount\": \"180000.0000\",\n    \"currency\": \"UGX\",\n    \"status\": \"SUCCEEDED\"\n  }\n}\n","json",[162,517,518,522,543,563,577,598,618,638,658,677,682],{"__ignoreMap":160},[165,519,520],{"class":167,"line":168},[165,521,350],{"class":195},[165,523,524,527,530,532,534,536,539,541],{"class":167,"line":189},[165,525,526],{"class":195},"  \"",[165,528,529],{"class":281},"event",[165,531,202],{"class":195},[165,533,307],{"class":195},[165,535,196],{"class":195},[165,537,538],{"class":175},"payment.succeeded",[165,540,202],{"class":195},[165,542,368],{"class":195},[165,544,545,547,550,552,554,556,559,561],{"class":167,"line":207},[165,546,526],{"class":195},[165,548,549],{"class":281},"timestamp",[165,551,202],{"class":195},[165,553,307],{"class":195},[165,555,196],{"class":195},[165,557,558],{"class":175},"2025-01-15T10:30:00.000Z",[165,560,202],{"class":195},[165,562,368],{"class":195},[165,564,565,567,570,572,574],{"class":167,"line":221},[165,566,526],{"class":195},[165,568,569],{"class":281},"data",[165,571,202],{"class":195},[165,573,307],{"class":195},[165,575,576],{"class":195}," {\n",[165,578,579,582,585,587,589,591,594,596],{"class":167,"line":328},[165,580,581],{"class":195},"    \"",[165,583,584],{"class":171},"id",[165,586,202],{"class":195},[165,588,307],{"class":195},[165,590,196],{"class":195},[165,592,593],{"class":175},"pay_abc123",[165,595,202],{"class":195},[165,597,368],{"class":195},[165,599,600,602,605,607,609,611,614,616],{"class":167,"line":353},[165,601,581],{"class":195},[165,603,604],{"class":171},"invoiceId",[165,606,202],{"class":195},[165,608,307],{"class":195},[165,610,196],{"class":195},[165,612,613],{"class":175},"inv_xyz789",[165,615,202],{"class":195},[165,617,368],{"class":195},[165,619,620,622,625,627,629,631,634,636],{"class":167,"line":371},[165,621,581],{"class":195},[165,623,624],{"class":171},"amount",[165,626,202],{"class":195},[165,628,307],{"class":195},[165,630,196],{"class":195},[165,632,633],{"class":175},"180000.0000",[165,635,202],{"class":195},[165,637,368],{"class":195},[165,639,640,642,645,647,649,651,654,656],{"class":167,"line":381},[165,641,581],{"class":195},[165,643,644],{"class":171},"currency",[165,646,202],{"class":195},[165,648,307],{"class":195},[165,650,196],{"class":195},[165,652,653],{"class":175},"UGX",[165,655,202],{"class":195},[165,657,368],{"class":195},[165,659,660,662,665,667,669,671,674],{"class":167,"line":386},[165,661,581],{"class":195},[165,663,664],{"class":171},"status",[165,666,202],{"class":195},[165,668,307],{"class":195},[165,670,196],{"class":195},[165,672,673],{"class":175},"SUCCEEDED",[165,675,676],{"class":195},"\"\n",[165,678,679],{"class":167,"line":393},[165,680,681],{"class":195},"  }\n",[165,683,685],{"class":167,"line":684},11,[165,686,687],{"class":195},"}\n",[138,689,690,692],{},[162,691,569],{}," is the object the event is about, in the same shape the API returns for it (a payment, an invoice, a collection, and so on).",[138,694,695],{},"These headers come with it:",[697,698,699,712],"table",{},[700,701,702],"thead",{},[703,704,705,709],"tr",{},[706,707,708],"th",{},"Header",[706,710,711],{},"Value",[713,714,715,729,739,749],"tbody",{},[703,716,717,723],{},[718,719,720],"td",{},[162,721,722],{},"X-Webhook-Signature-V2",[718,724,725,728],{},[162,726,727],{},"t=\u003Ctimestamp>,v1=\u003Chex HMAC-SHA256 of \"\u003Ctimestamp>.\u003Craw body>\">",", keyed with your webhook secret. Use this one.",[703,730,731,736],{},[718,732,733],{},[162,734,735],{},"X-Webhook-Timestamp",[718,737,738],{},"Unix seconds when the event was signed",[703,740,741,746],{},[718,742,743],{},[162,744,745],{},"X-Webhook-Signature",[718,747,748],{},"Hex HMAC-SHA256 of the raw body only (kept for older receivers; it does not protect against replays)",[703,750,751,756],{},[718,752,753],{},[162,754,755],{},"X-Event-Type",[718,757,758,759],{},"The event name, e.g. ",[162,760,538],{},[138,762,763,764,337],{},"Webhook URLs must point to a public internet address; private, loopback and cloud-metadata addresses are refused. Self-hosted deployments that deliver to an internal receiver can set ",[162,765,766],{},"ALLOW_PRIVATE_WEBHOOK_URLS=true",[142,768,770],{"id":769},"event-types","Event Types",[697,772,773,783],{},[700,774,775],{},[703,776,777,780],{},[706,778,779],{},"Event",[706,781,782],{},"Description",[713,784,785,802,812,822,835,848,858,868,884,897,910,922,932,942,952,962,978,994,1013,1026,1045],{},[703,786,787,799],{},[718,788,789,792,793,792,796],{},[162,790,791],{},"customer.created",", ",[162,794,795],{},"customer.updated",[162,797,798],{},"customer.deleted",[718,800,801],{},"A customer changed",[703,803,804,809],{},[718,805,806],{},[162,807,808],{},"subscription.created",[718,810,811],{},"A new subscription was created",[703,813,814,819],{},[718,815,816],{},[162,817,818],{},"subscription.renewed",[718,820,821],{},"A subscription moved into a new period",[703,823,824,832],{},[718,825,826,792,829],{},[162,827,828],{},"subscription.plan_changed",[162,830,831],{},"subscription.plan_change_scheduled",[718,833,834],{},"A plan change took effect, or was scheduled for the period end",[703,836,837,845],{},[718,838,839,792,842],{},[162,840,841],{},"subscription.paused",[162,843,844],{},"subscription.resumed",[718,846,847],{},"A subscription was paused or resumed",[703,849,850,855],{},[718,851,852],{},[162,853,854],{},"subscription.trial_ended",[718,856,857],{},"A trial ended",[703,859,860,865],{},[718,861,862],{},[162,863,864],{},"subscription.canceled",[718,866,867],{},"A subscription was canceled",[703,869,870,881],{},[718,871,872,792,875,792,878],{},[162,873,874],{},"invoice.finalized",[162,876,877],{},"invoice.paid",[162,879,880],{},"invoice.voided",[718,882,883],{},"An invoice changed state",[703,885,886,891],{},[718,887,888],{},[162,889,890],{},"invoice.partially_paid",[718,892,893,894,319],{},"A deposit or instalment was paid; the invoice stays open until payments cover it (",[496,895,896],{"href":94},"partial payments",[703,898,899,907],{},[718,900,901,792,904],{},[162,902,903],{},"payment.created",[162,905,906],{},"payment.pending",[718,908,909],{},"A payment was started, or the provider has not confirmed it yet",[703,911,912,919],{},[718,913,914,792,916],{},[162,915,538],{},[162,917,918],{},"payment.failed",[718,920,921],{},"A payment finished",[703,923,924,929],{},[718,925,926],{},[162,927,928],{},"payment.held_for_review",[718,930,931],{},"The provider reported a success whose amount or currency does not match the invoice; the invoice stays unpaid until you review it",[703,933,934,939],{},[718,935,936],{},[162,937,938],{},"payment.duplicate",[718,940,941],{},"Money arrived for an invoice that was already paid or voided; refund or apply it",[703,943,944,949],{},[718,945,946],{},[162,947,948],{},"payment.retry.succeeded",[718,950,951],{},"A retried payment went through",[703,953,954,959],{},[718,955,956],{},[162,957,958],{},"payment.refunded",[718,960,961],{},"A payment was refunded",[703,963,964,975],{},[718,965,966,792,969,792,972],{},[162,967,968],{},"credit_note.created",[162,970,971],{},"credit_note.finalized",[162,973,974],{},"credit_note.voided",[718,976,977],{},"A credit note changed",[703,979,980,991],{},[718,981,982,792,985,792,988],{},[162,983,984],{},"wallet.created",[162,986,987],{},"wallet.updated",[162,989,990],{},"wallet.terminated",[718,992,993],{},"A customer wallet changed",[703,995,996,1010],{},[718,997,998,792,1001,792,1004,792,1007],{},[162,999,1000],{},"collection.succeeded",[162,1002,1003],{},"collection.failed",[162,1005,1006],{},"collection.canceled",[162,1008,1009],{},"collection.stuck",[718,1011,1012],{},"A one-time collection finished, or the provider could not confirm it",[703,1014,1015,1023],{},[718,1016,1017,792,1020],{},[162,1018,1019],{},"payout.created",[162,1021,1022],{},"payout.requires_approval",[718,1024,1025],{},"A payout was created, or is waiting for approval",[703,1027,1028,1042],{},[718,1029,1030,792,1033,792,1036,792,1039],{},[162,1031,1032],{},"payout.succeeded",[162,1034,1035],{},"payout.failed",[162,1037,1038],{},"payout.canceled",[162,1040,1041],{},"payout.stuck",[718,1043,1044],{},"A payout finished, or the provider could not confirm it",[703,1046,1047,1052],{},[718,1048,1049],{},[162,1050,1051],{},"transfer.succeeded",[718,1053,1054],{},"Money moved between two wallets",[142,1056,1058],{"id":1057},"handling-webhooks","Handling Webhooks",[138,1060,1061,1062,1065,1066,1069,1070,337],{},"Check the signature against the raw body before trusting an event. This handler uses the standard ",[162,1063,1064],{},"Request"," and ",[162,1067,1068],{},"Response"," (Next.js route handlers, Hono, Bun, Deno, Cloudflare Workers); Express works the same way with ",[162,1071,1072],{},"express.raw()",[154,1074,1076],{"className":236,"code":1075,"language":239,"meta":160,"style":160},"import { createHmac, timingSafeEqual } from 'node:crypto';\n\nconst secret = process.env.PIMABILL_WEBHOOK_SECRET!;\n\nexport async function POST(request: Request): Promise\u003CResponse> {\n  const body = await request.text();\n  \u002F\u002F X-Webhook-Signature-V2: t=\u003Ctimestamp>,v1=\u003Csignature>\n  const parts = Object.fromEntries(\n    (request.headers.get('x-webhook-signature-v2') ?? '').split(',').map((p) => p.split('=', 2)),\n  );\n  const timestamp = Number(parts.t);\n  \u002F\u002F Reject old (replayed) events.\n  if (!timestamp || Math.abs(Date.now() \u002F 1000 - timestamp) > 300) {\n    return new Response('Stale or missing timestamp', { status: 401 });\n  }\n  const expected = createHmac('sha256', secret).update(`${parts.t}.${body}`).digest();\n  const given = Buffer.from(parts.v1 ?? '', 'hex');\n  if (given.length !== expected.length || !timingSafeEqual(given, expected)) {\n    return new Response('Invalid signature', { status: 401 });\n  }\n\n  const { event, data } = JSON.parse(body) as { event: string; data: { id: string } };\n  switch (event) {\n    case 'payment.succeeded':\n      \u002F\u002F Mark the order paid\n      break;\n    case 'subscription.canceled':\n      \u002F\u002F Revoke access\n      break;\n    default:\n      console.log(`Unhandled event: ${event} (${data.id})`);\n  }\n\n  return Response.json({ received: true });\n}\n",[162,1077,1078,1106,1110,1135,1139,1180,1204,1209,1229,1323,1330,1356,1362,1422,1460,1465,1533,1578,1623,1657,1662,1667,1733,1747,1762,1768,1776,1789,1795,1802,1810,1855,1860,1865,1896],{"__ignoreMap":160},[165,1079,1080,1082,1084,1087,1090,1093,1095,1097,1099,1102,1104],{"class":167,"line":168},[165,1081,247],{"class":246},[165,1083,250],{"class":195},[165,1085,1086],{"class":185}," createHmac",[165,1088,1089],{"class":195},",",[165,1091,1092],{"class":185}," timingSafeEqual",[165,1094,256],{"class":195},[165,1096,259],{"class":246},[165,1098,227],{"class":195},[165,1100,1101],{"class":175},"node:crypto",[165,1103,267],{"class":195},[165,1105,270],{"class":195},[165,1107,1108],{"class":167,"line":189},[165,1109,276],{"emptyLinePlaceholder":275},[165,1111,1112,1114,1117,1119,1122,1124,1127,1129,1132],{"class":167,"line":207},[165,1113,282],{"class":281},[165,1115,1116],{"class":185}," secret ",[165,1118,288],{"class":195},[165,1120,1121],{"class":185}," process",[165,1123,337],{"class":195},[165,1125,1126],{"class":185},"env",[165,1128,337],{"class":195},[165,1130,1131],{"class":185},"PIMABILL_WEBHOOK_SECRET",[165,1133,1134],{"class":195},"!;\n",[165,1136,1137],{"class":167,"line":221},[165,1138,276],{"emptyLinePlaceholder":275},[165,1140,1141,1144,1147,1150,1153,1155,1159,1161,1164,1167,1170,1173,1175,1178],{"class":167,"line":328},[165,1142,1143],{"class":246},"export",[165,1145,1146],{"class":281}," async",[165,1148,1149],{"class":281}," function",[165,1151,1152],{"class":294}," POST",[165,1154,297],{"class":195},[165,1156,1158],{"class":1157},"sHdIc","request",[165,1160,307],{"class":195},[165,1162,1163],{"class":171}," Request",[165,1165,1166],{"class":195},"):",[165,1168,1169],{"class":171}," Promise",[165,1171,1172],{"class":195},"\u003C",[165,1174,1068],{"class":171},[165,1176,1177],{"class":195},">",[165,1179,576],{"class":195},[165,1181,1182,1185,1188,1190,1192,1195,1197,1200,1202],{"class":167,"line":353},[165,1183,1184],{"class":281},"  const",[165,1186,1187],{"class":185}," body",[165,1189,405],{"class":195},[165,1191,408],{"class":246},[165,1193,1194],{"class":185}," request",[165,1196,337],{"class":195},[165,1198,1199],{"class":294},"text",[165,1201,422],{"class":303},[165,1203,270],{"class":195},[165,1205,1206],{"class":167,"line":371},[165,1207,1208],{"class":389},"  \u002F\u002F X-Webhook-Signature-V2: t=\u003Ctimestamp>,v1=\u003Csignature>\n",[165,1210,1211,1213,1216,1218,1221,1223,1226],{"class":167,"line":381},[165,1212,1184],{"class":281},[165,1214,1215],{"class":185}," parts",[165,1217,405],{"class":195},[165,1219,1220],{"class":185}," Object",[165,1222,337],{"class":195},[165,1224,1225],{"class":294},"fromEntries",[165,1227,1228],{"class":303},"(\n",[165,1230,1231,1234,1236,1238,1241,1243,1246,1248,1250,1253,1255,1258,1261,1264,1266,1268,1271,1273,1275,1277,1279,1281,1283,1286,1288,1290,1292,1294,1297,1300,1302,1304,1306,1308,1310,1312,1314,1318,1321],{"class":167,"line":386},[165,1232,1233],{"class":303},"    (",[165,1235,1158],{"class":185},[165,1237,337],{"class":195},[165,1239,1240],{"class":185},"headers",[165,1242,337],{"class":195},[165,1244,1245],{"class":294},"get",[165,1247,297],{"class":303},[165,1249,267],{"class":195},[165,1251,1252],{"class":175},"x-webhook-signature-v2",[165,1254,267],{"class":195},[165,1256,1257],{"class":303},") ",[165,1259,1260],{"class":195},"??",[165,1262,1263],{"class":195}," ''",[165,1265,319],{"class":303},[165,1267,337],{"class":195},[165,1269,1270],{"class":294},"split",[165,1272,297],{"class":303},[165,1274,267],{"class":195},[165,1276,1089],{"class":175},[165,1278,267],{"class":195},[165,1280,319],{"class":303},[165,1282,337],{"class":195},[165,1284,1285],{"class":294},"map",[165,1287,297],{"class":303},[165,1289,297],{"class":195},[165,1291,138],{"class":1157},[165,1293,319],{"class":195},[165,1295,1296],{"class":281}," =>",[165,1298,1299],{"class":185}," p",[165,1301,337],{"class":195},[165,1303,1270],{"class":294},[165,1305,297],{"class":303},[165,1307,267],{"class":195},[165,1309,288],{"class":175},[165,1311,267],{"class":195},[165,1313,1089],{"class":195},[165,1315,1317],{"class":1316},"sbssI"," 2",[165,1319,1320],{"class":303},"))",[165,1322,368],{"class":195},[165,1324,1325,1328],{"class":167,"line":393},[165,1326,1327],{"class":303},"  )",[165,1329,270],{"class":195},[165,1331,1332,1334,1337,1339,1342,1344,1347,1349,1352,1354],{"class":167,"line":684},[165,1333,1184],{"class":281},[165,1335,1336],{"class":185}," timestamp",[165,1338,405],{"class":195},[165,1340,1341],{"class":294}," Number",[165,1343,297],{"class":303},[165,1345,1346],{"class":185},"parts",[165,1348,337],{"class":195},[165,1350,1351],{"class":185},"t",[165,1353,319],{"class":303},[165,1355,270],{"class":195},[165,1357,1359],{"class":167,"line":1358},12,[165,1360,1361],{"class":389},"  \u002F\u002F Reject old (replayed) events.\n",[165,1363,1365,1368,1371,1374,1376,1379,1382,1384,1387,1389,1392,1394,1397,1400,1403,1406,1409,1411,1413,1415,1418,1420],{"class":167,"line":1364},13,[165,1366,1367],{"class":246},"  if",[165,1369,1370],{"class":303}," (",[165,1372,1373],{"class":195},"!",[165,1375,549],{"class":185},[165,1377,1378],{"class":195}," ||",[165,1380,1381],{"class":185}," Math",[165,1383,337],{"class":195},[165,1385,1386],{"class":294},"abs",[165,1388,297],{"class":303},[165,1390,1391],{"class":185},"Date",[165,1393,337],{"class":195},[165,1395,1396],{"class":294},"now",[165,1398,1399],{"class":303},"() ",[165,1401,1402],{"class":195},"\u002F",[165,1404,1405],{"class":1316}," 1000",[165,1407,1408],{"class":195}," -",[165,1410,1336],{"class":185},[165,1412,1257],{"class":303},[165,1414,1177],{"class":195},[165,1416,1417],{"class":1316}," 300",[165,1419,1257],{"class":303},[165,1421,350],{"class":195},[165,1423,1425,1428,1430,1433,1435,1437,1440,1442,1444,1446,1449,1451,1454,1456,1458],{"class":167,"line":1424},14,[165,1426,1427],{"class":246},"    return",[165,1429,291],{"class":195},[165,1431,1432],{"class":294}," Response",[165,1434,297],{"class":303},[165,1436,267],{"class":195},[165,1438,1439],{"class":175},"Stale or missing timestamp",[165,1441,267],{"class":195},[165,1443,1089],{"class":195},[165,1445,250],{"class":195},[165,1447,1448],{"class":303}," status",[165,1450,307],{"class":195},[165,1452,1453],{"class":1316}," 401",[165,1455,256],{"class":195},[165,1457,319],{"class":303},[165,1459,270],{"class":195},[165,1461,1463],{"class":167,"line":1462},15,[165,1464,681],{"class":195},[165,1466,1468,1470,1473,1475,1477,1479,1481,1484,1486,1488,1491,1493,1495,1498,1500,1503,1505,1507,1509,1511,1513,1516,1519,1522,1524,1526,1529,1531],{"class":167,"line":1467},16,[165,1469,1184],{"class":281},[165,1471,1472],{"class":185}," expected",[165,1474,405],{"class":195},[165,1476,1086],{"class":294},[165,1478,297],{"class":303},[165,1480,267],{"class":195},[165,1482,1483],{"class":175},"sha256",[165,1485,267],{"class":195},[165,1487,1089],{"class":195},[165,1489,1490],{"class":185}," secret",[165,1492,319],{"class":303},[165,1494,337],{"class":195},[165,1496,1497],{"class":294},"update",[165,1499,297],{"class":303},[165,1501,1502],{"class":195},"`${",[165,1504,1346],{"class":185},[165,1506,337],{"class":195},[165,1508,1351],{"class":185},[165,1510,374],{"class":195},[165,1512,337],{"class":175},[165,1514,1515],{"class":195},"${",[165,1517,1518],{"class":185},"body",[165,1520,1521],{"class":195},"}`",[165,1523,319],{"class":303},[165,1525,337],{"class":195},[165,1527,1528],{"class":294},"digest",[165,1530,422],{"class":303},[165,1532,270],{"class":195},[165,1534,1536,1538,1541,1543,1546,1548,1551,1553,1555,1557,1560,1563,1565,1567,1569,1572,1574,1576],{"class":167,"line":1535},17,[165,1537,1184],{"class":281},[165,1539,1540],{"class":185}," given",[165,1542,405],{"class":195},[165,1544,1545],{"class":185}," Buffer",[165,1547,337],{"class":195},[165,1549,1550],{"class":294},"from",[165,1552,297],{"class":303},[165,1554,1346],{"class":185},[165,1556,337],{"class":195},[165,1558,1559],{"class":185},"v1",[165,1561,1562],{"class":195}," ??",[165,1564,1263],{"class":195},[165,1566,1089],{"class":195},[165,1568,227],{"class":195},[165,1570,1571],{"class":175},"hex",[165,1573,267],{"class":195},[165,1575,319],{"class":303},[165,1577,270],{"class":195},[165,1579,1581,1583,1585,1588,1590,1593,1596,1598,1600,1602,1604,1607,1610,1612,1614,1616,1618,1621],{"class":167,"line":1580},18,[165,1582,1367],{"class":246},[165,1584,1370],{"class":303},[165,1586,1587],{"class":185},"given",[165,1589,337],{"class":195},[165,1591,1592],{"class":185},"length",[165,1594,1595],{"class":195}," !==",[165,1597,1472],{"class":185},[165,1599,337],{"class":195},[165,1601,1592],{"class":185},[165,1603,1378],{"class":195},[165,1605,1606],{"class":195}," !",[165,1608,1609],{"class":294},"timingSafeEqual",[165,1611,297],{"class":303},[165,1613,1587],{"class":185},[165,1615,1089],{"class":195},[165,1617,1472],{"class":185},[165,1619,1620],{"class":303},")) ",[165,1622,350],{"class":195},[165,1624,1626,1628,1630,1632,1634,1636,1639,1641,1643,1645,1647,1649,1651,1653,1655],{"class":167,"line":1625},19,[165,1627,1427],{"class":246},[165,1629,291],{"class":195},[165,1631,1432],{"class":294},[165,1633,297],{"class":303},[165,1635,267],{"class":195},[165,1637,1638],{"class":175},"Invalid signature",[165,1640,267],{"class":195},[165,1642,1089],{"class":195},[165,1644,250],{"class":195},[165,1646,1448],{"class":303},[165,1648,307],{"class":195},[165,1650,1453],{"class":1316},[165,1652,256],{"class":195},[165,1654,319],{"class":303},[165,1656,270],{"class":195},[165,1658,1660],{"class":167,"line":1659},20,[165,1661,681],{"class":195},[165,1663,1665],{"class":167,"line":1664},21,[165,1666,276],{"emptyLinePlaceholder":275},[165,1668,1670,1672,1674,1677,1679,1682,1684,1686,1689,1691,1694,1696,1698,1700,1703,1705,1707,1709,1712,1715,1717,1719,1721,1724,1726,1728,1730],{"class":167,"line":1669},22,[165,1671,1184],{"class":281},[165,1673,250],{"class":195},[165,1675,1676],{"class":185}," event",[165,1678,1089],{"class":195},[165,1680,1681],{"class":185}," data",[165,1683,256],{"class":195},[165,1685,405],{"class":195},[165,1687,1688],{"class":185}," JSON",[165,1690,337],{"class":195},[165,1692,1693],{"class":294},"parse",[165,1695,297],{"class":303},[165,1697,1518],{"class":185},[165,1699,1257],{"class":303},[165,1701,1702],{"class":246},"as",[165,1704,250],{"class":195},[165,1706,1676],{"class":303},[165,1708,307],{"class":195},[165,1710,1711],{"class":171}," string",[165,1713,1714],{"class":195},";",[165,1716,1681],{"class":303},[165,1718,307],{"class":195},[165,1720,250],{"class":195},[165,1722,1723],{"class":303}," id",[165,1725,307],{"class":195},[165,1727,1711],{"class":171},[165,1729,256],{"class":195},[165,1731,1732],{"class":195}," };\n",[165,1734,1736,1739,1741,1743,1745],{"class":167,"line":1735},23,[165,1737,1738],{"class":246},"  switch",[165,1740,1370],{"class":303},[165,1742,529],{"class":185},[165,1744,1257],{"class":303},[165,1746,350],{"class":195},[165,1748,1750,1753,1755,1757,1759],{"class":167,"line":1749},24,[165,1751,1752],{"class":246},"    case",[165,1754,227],{"class":195},[165,1756,538],{"class":175},[165,1758,267],{"class":195},[165,1760,1761],{"class":195},":\n",[165,1763,1765],{"class":167,"line":1764},25,[165,1766,1767],{"class":389},"      \u002F\u002F Mark the order paid\n",[165,1769,1771,1774],{"class":167,"line":1770},26,[165,1772,1773],{"class":246},"      break",[165,1775,270],{"class":195},[165,1777,1779,1781,1783,1785,1787],{"class":167,"line":1778},27,[165,1780,1752],{"class":246},[165,1782,227],{"class":195},[165,1784,864],{"class":175},[165,1786,267],{"class":195},[165,1788,1761],{"class":195},[165,1790,1792],{"class":167,"line":1791},28,[165,1793,1794],{"class":389},"      \u002F\u002F Revoke access\n",[165,1796,1798,1800],{"class":167,"line":1797},29,[165,1799,1773],{"class":246},[165,1801,270],{"class":195},[165,1803,1805,1808],{"class":167,"line":1804},30,[165,1806,1807],{"class":246},"    default",[165,1809,1761],{"class":195},[165,1811,1813,1816,1818,1821,1823,1826,1829,1831,1833,1835,1837,1839,1841,1843,1845,1847,1849,1851,1853],{"class":167,"line":1812},31,[165,1814,1815],{"class":185},"      console",[165,1817,337],{"class":195},[165,1819,1820],{"class":294},"log",[165,1822,297],{"class":303},[165,1824,1825],{"class":195},"`",[165,1827,1828],{"class":175},"Unhandled event: ",[165,1830,1515],{"class":195},[165,1832,529],{"class":185},[165,1834,374],{"class":195},[165,1836,1370],{"class":175},[165,1838,1515],{"class":195},[165,1840,569],{"class":185},[165,1842,337],{"class":195},[165,1844,584],{"class":185},[165,1846,374],{"class":195},[165,1848,319],{"class":175},[165,1850,1825],{"class":195},[165,1852,319],{"class":303},[165,1854,270],{"class":195},[165,1856,1858],{"class":167,"line":1857},32,[165,1859,681],{"class":195},[165,1861,1863],{"class":167,"line":1862},33,[165,1864,276],{"emptyLinePlaceholder":275},[165,1866,1868,1871,1873,1875,1877,1879,1881,1884,1886,1890,1892,1894],{"class":167,"line":1867},34,[165,1869,1870],{"class":246},"  return",[165,1872,1432],{"class":185},[165,1874,337],{"class":195},[165,1876,515],{"class":294},[165,1878,297],{"class":303},[165,1880,300],{"class":195},[165,1882,1883],{"class":303}," received",[165,1885,307],{"class":195},[165,1887,1889],{"class":1888},"sfNiH"," true",[165,1891,256],{"class":195},[165,1893,319],{"class":303},[165,1895,270],{"class":195},[165,1897,1899],{"class":167,"line":1898},35,[165,1900,687],{"class":195},[138,1902,1903],{},"The same check in Python:",[154,1905,1907],{"className":427,"code":1906,"language":430,"meta":160,"style":160},"import hashlib\nimport hmac\n\n\nimport time\n\n\ndef is_valid(body: bytes, signature_v2: str, secret: str, tolerance: int = 300) -> bool:\n    parts = dict(p.split(\"=\", 1) for p in signature_v2.split(\",\") if \"=\" in p)\n    timestamp = parts.get(\"t\", \"\")\n    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:\n        return False\n    signed = timestamp.encode() + b\".\" + body\n    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, parts.get(\"v1\", \"\"))\n",[162,1908,1909,1914,1919,1923,1927,1932,1936,1940,1945,1950,1955,1960,1965,1970,1975],{"__ignoreMap":160},[165,1910,1911],{"class":167,"line":168},[165,1912,1913],{},"import hashlib\n",[165,1915,1916],{"class":167,"line":189},[165,1917,1918],{},"import hmac\n",[165,1920,1921],{"class":167,"line":207},[165,1922,276],{"emptyLinePlaceholder":275},[165,1924,1925],{"class":167,"line":221},[165,1926,276],{"emptyLinePlaceholder":275},[165,1928,1929],{"class":167,"line":328},[165,1930,1931],{},"import time\n",[165,1933,1934],{"class":167,"line":353},[165,1935,276],{"emptyLinePlaceholder":275},[165,1937,1938],{"class":167,"line":371},[165,1939,276],{"emptyLinePlaceholder":275},[165,1941,1942],{"class":167,"line":381},[165,1943,1944],{},"def is_valid(body: bytes, signature_v2: str, secret: str, tolerance: int = 300) -> bool:\n",[165,1946,1947],{"class":167,"line":386},[165,1948,1949],{},"    parts = dict(p.split(\"=\", 1) for p in signature_v2.split(\",\") if \"=\" in p)\n",[165,1951,1952],{"class":167,"line":393},[165,1953,1954],{},"    timestamp = parts.get(\"t\", \"\")\n",[165,1956,1957],{"class":167,"line":684},[165,1958,1959],{},"    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:\n",[165,1961,1962],{"class":167,"line":1358},[165,1963,1964],{},"        return False\n",[165,1966,1967],{"class":167,"line":1364},[165,1968,1969],{},"    signed = timestamp.encode() + b\".\" + body\n",[165,1971,1972],{"class":167,"line":1424},[165,1973,1974],{},"    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()\n",[165,1976,1977],{"class":167,"line":1462},[165,1978,1979],{},"    return hmac.compare_digest(expected, parts.get(\"v1\", \"\"))\n",[138,1981,1982,1983,1986],{},"Respond with a 2xx quickly and do slow work afterwards. Events can arrive more than once, and not always in order: use ",[162,1984,1985],{},"data.id"," and the object's status, not the arrival order.",[142,1988,1990],{"id":1989},"retries","Retries",[138,1992,1993],{},"A delivery fails when your endpoint answers with a non-2xx status or does not answer within 10 seconds. Each event is attempted 3 times, with exponential backoff starting at 2 seconds. Every attempt is recorded in the webhook log:",[150,1995,1996,2180],{"sync":152},[154,1997,1999],{"className":236,"code":1998,"filename":238,"language":239,"meta":160,"style":160},"const logs = await client.tenants.getWebhookLogs({ direction: 'outbound', limit: 20 });\n\nfor (const log of logs.data) {\n  console.log(`${log.createdAt} ${log.event}: ${log.success ? 'delivered' : `failed (${log.statusCode ?? 'no response'})`}`);\n}\n",[162,2000,2001,2055,2059,2084,2176],{"__ignoreMap":160},[165,2002,2003,2005,2008,2010,2012,2014,2016,2018,2020,2023,2025,2027,2030,2032,2034,2037,2039,2041,2044,2046,2049,2051,2053],{"class":167,"line":168},[165,2004,282],{"class":281},[165,2006,2007],{"class":185}," logs ",[165,2009,288],{"class":195},[165,2011,408],{"class":246},[165,2013,334],{"class":185},[165,2015,337],{"class":195},[165,2017,340],{"class":185},[165,2019,337],{"class":195},[165,2021,2022],{"class":294},"getWebhookLogs",[165,2024,297],{"class":185},[165,2026,300],{"class":195},[165,2028,2029],{"class":303}," direction",[165,2031,307],{"class":195},[165,2033,227],{"class":195},[165,2035,2036],{"class":175},"outbound",[165,2038,267],{"class":195},[165,2040,1089],{"class":195},[165,2042,2043],{"class":303}," limit",[165,2045,307],{"class":195},[165,2047,2048],{"class":1316}," 20",[165,2050,256],{"class":195},[165,2052,319],{"class":185},[165,2054,270],{"class":195},[165,2056,2057],{"class":167,"line":189},[165,2058,276],{"emptyLinePlaceholder":275},[165,2060,2061,2064,2066,2068,2071,2074,2077,2079,2082],{"class":167,"line":207},[165,2062,2063],{"class":246},"for",[165,2065,1370],{"class":185},[165,2067,282],{"class":281},[165,2069,2070],{"class":185}," log ",[165,2072,2073],{"class":195},"of",[165,2075,2076],{"class":185}," logs",[165,2078,337],{"class":195},[165,2080,2081],{"class":185},"data) ",[165,2083,350],{"class":195},[165,2085,2086,2089,2091,2093,2095,2097,2099,2101,2104,2106,2109,2111,2113,2115,2117,2120,2122,2124,2126,2129,2132,2134,2137,2139,2142,2145,2148,2150,2152,2154,2157,2159,2161,2164,2167,2169,2172,2174],{"class":167,"line":221},[165,2087,2088],{"class":185},"  console",[165,2090,337],{"class":195},[165,2092,1820],{"class":294},[165,2094,297],{"class":303},[165,2096,1502],{"class":195},[165,2098,1820],{"class":185},[165,2100,337],{"class":195},[165,2102,2103],{"class":185},"createdAt",[165,2105,374],{"class":195},[165,2107,2108],{"class":195}," ${",[165,2110,1820],{"class":185},[165,2112,337],{"class":195},[165,2114,529],{"class":185},[165,2116,374],{"class":195},[165,2118,2119],{"class":175},": ",[165,2121,1515],{"class":195},[165,2123,1820],{"class":185},[165,2125,337],{"class":195},[165,2127,2128],{"class":185},"success ",[165,2130,2131],{"class":195},"?",[165,2133,227],{"class":195},[165,2135,2136],{"class":175},"delivered",[165,2138,267],{"class":195},[165,2140,2141],{"class":195}," :",[165,2143,2144],{"class":195}," `",[165,2146,2147],{"class":175},"failed (",[165,2149,1515],{"class":195},[165,2151,1820],{"class":185},[165,2153,337],{"class":195},[165,2155,2156],{"class":185},"statusCode ",[165,2158,1260],{"class":195},[165,2160,227],{"class":195},[165,2162,2163],{"class":175},"no response",[165,2165,2166],{"class":195},"'}",[165,2168,319],{"class":175},[165,2170,2171],{"class":195},"`}`",[165,2173,319],{"class":303},[165,2175,270],{"class":195},[165,2177,2178],{"class":167,"line":328},[165,2179,687],{"class":195},[154,2181,2183],{"className":427,"code":2182,"filename":429,"language":430,"meta":160,"style":160},"logs = client.tenants.get_webhook_logs(direction=\"outbound\", limit=20)\n\nfor log in logs.data:\n    print(f\"{log.created_at} {log.event}: {'delivered' if log.success else 'failed'}\")\n",[162,2184,2185,2190,2194,2199],{"__ignoreMap":160},[165,2186,2187],{"class":167,"line":168},[165,2188,2189],{},"logs = client.tenants.get_webhook_logs(direction=\"outbound\", limit=20)\n",[165,2191,2192],{"class":167,"line":189},[165,2193,276],{"emptyLinePlaceholder":275},[165,2195,2196],{"class":167,"line":207},[165,2197,2198],{},"for log in logs.data:\n",[165,2200,2201],{"class":167,"line":221},[165,2202,2203],{},"    print(f\"{log.created_at} {log.event}: {'delivered' if log.success else 'failed'}\")\n",[142,2205,2207],{"id":2206},"inbound-webhooks-payment-providers","Inbound Webhooks (Payment Providers)",[138,2209,2210],{},"PimaBill also receives notifications from payment providers to update payment statuses. Point each provider's webhook setting at your API:",[697,2212,2213,2223],{},[700,2214,2215],{},[703,2216,2217,2220],{},[706,2218,2219],{},"Provider",[706,2221,2222],{},"URL",[713,2224,2225,2235,2245,2255,2265,2275,2285,2295],{},[703,2226,2227,2230],{},[718,2228,2229],{},"Stripe",[718,2231,2232],{},[162,2233,2234],{},"POST \u002Fwebhooks\u002Fstripe",[703,2236,2237,2240],{},[718,2238,2239],{},"Paystack",[718,2241,2242],{},[162,2243,2244],{},"POST \u002Fwebhooks\u002Fpaystack",[703,2246,2247,2250],{},[718,2248,2249],{},"Flutterwave",[718,2251,2252],{},[162,2253,2254],{},"POST \u002Fwebhooks\u002Fflutterwave",[703,2256,2257,2260],{},[718,2258,2259],{},"PayPal",[718,2261,2262],{},[162,2263,2264],{},"POST \u002Fwebhooks\u002Fpaypal",[703,2266,2267,2270],{},[718,2268,2269],{},"Pesapal",[718,2271,2272],{},[162,2273,2274],{},"POST \u002Fwebhooks\u002Fpesapal",[703,2276,2277,2280],{},[718,2278,2279],{},"PayU",[718,2281,2282],{},[162,2283,2284],{},"POST \u002Fwebhooks\u002Fpayu",[703,2286,2287,2290],{},[718,2288,2289],{},"DPO",[718,2291,2292],{},[162,2293,2294],{},"POST \u002Fwebhooks\u002Fdpo",[703,2296,2297,2300],{},[718,2298,2299],{},"Any provider, per organisation",[718,2301,2302],{},[162,2303,2304],{},"POST \u002Fwebhooks\u002F{provider}\u002F{tenantId}",[138,2306,2307,2308,337],{},"Mobile money and crypto providers used for collections and payouts are sent their notification URL with each request, so there is nothing to configure, except Airtel Money: set its callback once in the Airtel portal to ",[162,2309,2310],{},"POST \u002Fwebhooks\u002Fairtel_money\u002F{tenantId}\u002Fcallback",[2312,2313,2314],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":160,"searchDepth":189,"depth":189,"links":2316},[2317,2318,2319,2320,2321,2322],{"id":144,"depth":189,"text":145},{"id":502,"depth":189,"text":503},{"id":769,"depth":189,"text":770},{"id":1057,"depth":189,"text":1058},{"id":1989,"depth":189,"text":1990},{"id":2206,"depth":189,"text":2207},"Set up real-time webhook notifications for billing events.","md",null,{},{"title":117,"description":2323},"Fc4M69yozvUAKb1Iheh973WE-xa_k_ceG12q-sMYTgo",[2330,2332],{"title":113,"path":114,"stem":115,"description":2331,"children":-1},"Track revenue, subscriptions, customers, and payment performance with built-in analytics.",{"title":121,"path":122,"stem":123,"description":2333,"children":-1},"Test your integration with test money before going live.",1793401242783]