Repository navigation
1.9.79: a dead LTV webhook can be revived, and is sent what it missed in the last 7 days - #289
Merged
Merged
Conversation
LTV webhooks were queued by every /ltv write and sent by nothing a
default install runs: 202-cronjobs/ltv_webhooks.php was the only
dispatcher, and every default scheduler (the installer's cron line,
install.sh, both Docker cron services, AutoCron) fetches index.php
alone. EveryCronJobIsScheduledByDefaultTest listed it as NOT_SCHEDULED
("opt-in, predates the measurement rewrite": history, not a reason). The
Landing Page Optimizer's pairing webhook waited with the rest.
The dispatch moves into Prosper202\Ltv\WebhookDispatcher, and two entry
points run it: the minutely tier of index.php (last, with a 15-second
budget) and ltv_webhooks.php on its own (--budget=N, default 50), which
the Coolify worker service now runs too. Both take one MySQL named
lock, as the attribution worker and the Android intake do, and each
delivery is still claimed by compare-and-set before it is sent, so runs
the lock does not see cannot send it twice either.
- The deadline is asked before each delivery, at least one a run, so a
run ends at most one delivery past it.
- A webhook whose delivery took 2 s or more to fail is not sent to again
in that run; its other deliveries stay due, untried, and a cursor on
(next_attempt_at, delivery_id) steps past them. A receiver that times
out costs one timeout a run instead of the budget every webhook on the
install shares. A failure that comes back at once is retried at its own
pace, as before.
- Retries and backoff are unchanged (recordAttempt(): 2^n minutes, failed
after 6, the webhook marked dead). A refusal, a failed lookup or a send
that throws fails its own delivery and the run goes on (#24).
- A delivery still unsent 7 days after it was queued is failed with
"expired: ..." as its last response, never sent, its webhook left
active: an upgraded install sends the last week of its backlog, oldest
first, rather than months of stale events. Expired 1,000 a statement;
a run whose budget ends first sends nothing, since what is left to
expire is the oldest due.
- The response body is stored as UTF-8 cut at a character boundary: a
body that is not UTF-8 was refused under a strict sql_mode, the outcome
went unrecorded and the delivery would have been sent again.
- post() builds every option through requestOptions(), the guard's set
winning over the POST's, and refuses to send when curl refuses one.
Tests: WebhookDispatchIntegrationTest (a real server, a php -S receiver,
two child processes for the overlapping runs, under the lock and
without it), WebhookDispatcherTest, LtvWebhooksAreSentEveryMinuteTest
(the minutely tier, last, with its budget; the Coolify worker), and the
scheduling, guard-parity and LTV server-failure tests updated. Each
removal planted was caught by a named test. tests/live/ltv-webhooks.sh
is the pass end to end against an https receiver.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
1.9.79 gained #262. The only conflict was 202-config/changelogs.json, where both sides added 1.9.79 lines: 1.9.79's lines are kept and this branch's follow them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in what 1.9.79 gained since this branch last merged it. The conflicts were only in the changelogs, where both sides added 1.9.79 lines: 1.9.79's lines are kept as it has them and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
… in the last 7 days A webhook is marked dead when one delivery fails 6 times, about an hour of retries now that #273 sends them, and nothing could make it active again: LTV Settings said to register it again, which for the Landing Page Optimizer, whose pairing keeps the webhook's id and secret, meant pairing again. A dead webhook was also queued nothing, and the deliveries it had pending spent their remaining attempts on "endpoint not active", so the events of an outage were lost for good. - MysqlWebhookRepository::revive() makes a webhook that is not active active again with its id, URL and secret, resets its pending deliveries' attempts to 0, and (requeue, the default) makes every delivery of it not delivered and queued within the dispatcher's 7 days pending again, due now: the one that killed it and the ones held while it was dead. A delivered one is never touched. It locks the webhook row first, and an active webhook is left as it is. - While dead, enqueue() still queues its events, and the dispatcher holds each (holdDelivery(): failed, attempts as they were, "held: …"), never trying it. The hold asks the webhook's status again in its own UPDATE, so a delivery read while the webhook was dead and revived since is sent. - PUT /ltv/webhooks/{id} {"status": "active", "requeue": bool}, LTV Settings' Revive button on a dead row (its confirm names how many it missed), p202 ltv webhooks revive <id> [--requeue=false] and bin/p202 ltv:webhook:revive <id> [--no-requeue]. requeue is the default: the receiver was down, the events it missed are what keeps a subscriber (the optimizer's conversions, a CRM's revenue) consistent, the 7 days are the window the dispatcher already applies, and every one goes with its own X-P202-Delivery, so a receiver that did take one de-duplicates it. requeue false is there for a receiver that was replaced and wants new events only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
Contributor
This was referenced Oct 28, 2026
… and waits for a run that is sending revive() with requeue false only reset the pending deliveries' attempts and made the webhook active, so the next run sent every delivery queued while it was dead that no run had held yet, and every one waiting out a retry's backoff: "new events only" sent old ones (Codex, on #296). Without requeue, every delivery of the webhook still pending is now held in the revival's transaction, as a dead webhook's are: failed, its attempts as they were, "held: its webhook was revived for new events only, …", never sent. The answer says how many (held). With requeue, the pending ones are sent: their attempts go back to 0 and each keeps its due time, so one waiting out a backoff waits at most 32 minutes. A claim (claimDelivery()) reads the same as a backoff, so a due time is never shortened. The failed ones it sends again are due at their queueing rather than at the revival, so they go oldest first with the pending ones no run has held yet. A delivery a run has claimed and not yet recorded must not be held or made due under it: a hold would be overwritten by the run's record (a held delivery sent anyway), and a requeued one could be claimed again while the first send is in flight. revive() now takes the dispatcher's lock (WebhookDispatcher::betweenRuns()), waiting up to 20 seconds for a run in flight; a run still sending then is an LtvConflictException, a 409 through the API with nothing changed, and the Go CLI hints to run it again in a minute rather than "update it instead". Tests: WebhookReviveIntegrationTest gains a pending row at revival time both ways and a revival from a second session while a run has claimed a delivery; each failed against a78e59a. The live pass revives with a delivery still pending, both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
tracking202
pushed a commit
that referenced
this pull request
Oct 28, 2026
…v-delete-webhook-id Brings in #289's fix for a revival without requeue, which takes the dispatcher's lock and can answer a conflict. The conflicts were only in changelogs.txt and tests/live/README.md: #289's revised revive lines are kept as it has them, and this branch's lines follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
tracking202
pushed a commit
that referenced
this pull request
Oct 28, 2026
#289's revive() now waits up to 20 seconds for a dispatcher run still sending and then throws LtvConflictException with nothing changed. The page showed the repository's API-worded sentence; it now says, in its own words, that webhook #N was not revived because deliveries are being sent right now, nothing was changed, and to revive it again in a minute. LtvSettingsPostedValuesIntegrationTest holds the dispatcher's lock while the page revives a dead webhook: the sentence, and the webhook still dead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
This was referenced Oct 28, 2026
Brings in 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, changelogs.txt, tests/live/README.md, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in ltv-webhooks-scheduled (this branch's stacked base), and through it 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #273 (QSB2,
1.9.79-ltv-webhooks-scheduled, at 272f792): review and merge this after #273. Until #273 merges, this diff includes #273's commits; the changes of its own are commits a78e59a and 7ac4aa9 (the fix after review, below). It is not stacked on #285 (9BA0): the two share only changelog and doc files, in different sections.Backlog: 8YRG
What was wrong
deadwhen one delivery fails 6 times. Now that 1.9.79: the minutely cron sends LTV webhooks, within a budget #273 sends deliveries, that is about an hour of retries. Nothing in the API, the UI or either CLI could make it active again. LTV Settings' tooltip said "fix the endpoint and re-register". The Landing Page Optimizer's pairing stores the webhook's id and the t202ctx key derived from its secret, so re-registering meant pairing again.enqueue()queued nothing for a dead webhook, so every event during the outage was gone. The deliveries it had pending were spent:endpoint not activeused up their remaining attempts, so even a database edit back toactivebrought nothing back.What changed
MysqlWebhookRepository::enqueue()queues foractiveanddeadwebhooks. When the dispatcher reaches a dead webhook's delivery, it holds it (holdDelivery()): the delivery becomesfailed, keeps the attempts it had, getsheld: its webhook is dead, so it was not sent; …as its last response, and is never tried.UPDATE … JOIN(w.status = 'dead'). So a delivery the dispatcher read while the webhook was dead, but which was revived since, is not held: the dispatcher sends it. There is a test for that interleaving.enqueue()needs no lock. An event queued while a revival commits is never held for a webhook that is active by then.MysqlWebhookRepository::revive($userId, $webhookId, $requeue, $now)first takes the dispatcher's lock (WebhookDispatcher::betweenRuns()), waiting up to 20 s for a run in flight; a run still sending then is anLtvConflictException(409) and nothing changes. It then runs in one transaction that locks the webhook row (FOR UPDATE):activewith the same id, URL and secret;$requeue(the default), every delivery of it that isfailedand was queued withinWebhookDispatcher::MAX_AGE_SECONDS(1.9.79: the minutely cron sends LTV webhooks, within a budget #273's 7 days) becomes pending again, with 0 attempts, due at its queueing. That covers the delivery that killed it and the held ones. Its pending deliveries are sent too: their attempts go back to 0, so the next failure does not kill it again at once ("reviving resets the failure count": a webhook dies on the 6th failure of one delivery, and there is no per-webhook counter), and each keeps its due time. The dispatcher sends them oldest first, each with its ownX-P202-Delivery. Adeliveredrow is never touched, so nothing that was delivered is sent twice. Older rows stayfailed;$requeue, every delivery of it still pending is held as the dead path holds them:failed, its attempts as they were,held: its webhook was revived for new events only, so it was not sent;revived: false). Another account's webhook is aRecordNotFoundException.PUT /ltv/webhooks/{id}(ltv:write) takes{"status": "active", "requeue": true|false}.PayloadKeys::refuseUnknown). Any other status is a 422 onstatus, because the server alone marks a webhook dead andDELETEremoves one.requeueis read withRequestFlag, so"maybe"is a 422 onrequeue.revived,requeued,attempts_reset,heldandrequeue. Another account's webhook is a 404, and a revival behind a run still sending after 20 s a 409 (the Go CLI's hint: run it again in a minute).dry_run; the dispatcher refuses both as before.missedCounts()). It posts the page's token like the Delete button, and the page answers "Webhook #N is active again; K delivery(ies) it missed in the last 7 days will be sent on the next cron run." Thedeadpill's tooltip now says to fix the endpoint, then Revive.p202 ltv webhooks revive <webhook-id> [--requeue=false], with a search task ("revive webhook", "reactivate webhook", …).bin/p202 ltv:webhook:revive <id> [--no-requeue]. The PHP CLI had no webhook command before.documentation/api/20-ltv.md("A dead webhook"),docs/openapi.yaml(the PUT, and "held" on the delivery log),docs/cli-agent.md,documentation/cli/10-go-cli.md, and both changelogs.Decision: re-queueing is the default
requeuedefaults to true:"requeue": false(--requeue=false,--no-requeue) is for a receiver that was replaced and wants new events only.Fix after review:
requeue=falseand the deliveries still pending (7ac4aa9)Codex found this on #296, which is stacked on this PR (thread). With
requeue=false,revive()only reset the pending rows' attempts. The next run then sent every delivery queued while the webhook was dead that no run had held yet, and every one waiting out a retry's backoff: "new events only" sent old ones. Against a78e59a the new test's revival answeredattempts_reset: 1and left both rows pending.requeue=falsenow holds every pending delivery of the webhook inside the revival's transaction, as above. The answer'sheldsays how many.requeue=truesends the pending ones. Each keeps its due time, so one in backoff waits at most 32 minutes. A claim (claimDelivery(),next_attempt_at = now + 300) reads the same as a backoff, so a due time is never shortened. The failed ones it sends again are now due at their queueing rather than at the revival. Before, a delivery queued while dead that no run had held yet went ahead of older ones it sent again: the new test received them as 2, 1.recordAttempt()has no status condition. A hold made while the run was sending would have been overwritten by the run's record, so a held delivery was sent anyway. And a requeued row made due could be claimed again during the first send. The revival therefore takes the dispatcher's own lock and waits for the run. The dispatcher sends only under that lock (runExclusive(), fromindex.phpandltv_webhooks.php), so no claim is in flight while the revival changes rows. A run still sending after 20 s is a 409 and nothing changes. A revival on the run's own session re-enters the lock (testARevivalBetweenTheDispatchersReadAndItsHoldIsSentdoes that, standing in for a run the lock cannot see).The Landing Page Optimizer
The pairing in
202-account/api-integrations.phpcreates a wildcard webhook. It storeswebhook_idandctx_key = HMAC(secret, 't202ctx-v1')inlpo_bridge_config, and hands the secret to the SaaS once. After that, three paths read the webhook by id regardless of its status, and none of them changes on a revival:DimensionSync::pushForUser()(the secret, by id and user);bridge_config.php(the secret checks the config's signature);ctx_key).The only thing the death stopped was the events, and a revival restores them.
testARevivedLpoPairingWebhookGoesOnWithoutPairingAgaintests this:EventBridge::emit()while dead is held;LtvController::updateWebhook()sends it again;lpo_bridge_configstill names the same webhook, and itsctx_keystill equals the key derived from the webhook's stored secret.Tests
tests/Ltv/WebhookReviveIntegrationTest.php(@group integration, scratch database, strictsql_mode, 1.9.79: the minutely cron sends LTV webhooks, within a budget #273's loopback receiver), 11 tests:requeuefalse: only new events are sent;requeuefalse: held (failed, its attempts, theheld:reason), and only the event queued after the revival is sent (7ac4aa9);requeuetrue: sent once, oldest first with those sent again, and the one in backoff at its time, with 0 attempts (7ac4aa9);LtvConflictException), nothing held, the delivery recorded once; after the run the revival goes through (7ac4aa9);LtvController: the answer, no secret, the refusals (statusmissing,dead,disabled,requeue: maybe, an unknown key), another account's 404;go-cli/cmd/system_admin_test.goTestLtvWebhookReviveSendsTheRevival: the PUT and its body with and without--requeue=false; a bad id and--requeue=mayberefused before anything is sent (exit 1); a 404's hint namesp202 ltv webhooks list; a 409's hint says to run it again in a minute, not "update it instead". The existingTestEveryLtvWriteRefusesStagedBeforeSendingAnythingpicks the new command up: 26 LTV writes refuse--staged.tests/Cli/Commands/LtvWebhookReviveCommandTest.php: the PUT with and without--no-requeue; a bad id refused before any request.WebhookDispatcherTest(the summary'sheld), andWebhookDispatchIntegrationTest's report type. Itsdisabledcase keeps the oldendpoint not activepath, whichdisabledstill takes.cmpand restored. All were caught by a named test.…StillPendingAreHeld); sent-again rows due at the revival (…StillPendingAreSentOnce, order); pending rows made due now (the same test, "keeps its time"); the revival not waiting for the lock (testARevivalWaitsForARunThatHasClaimedADelivery); the lock never released (the same test, which errors with the revival's ownLtvConflictException); and the Go 409 hint dropped (TestLtvWebhookReviveSendsTheRevival).The 19 plants
testADeadWebhookHolds…,testOnlyTheLastSevenDays…,testARevivedLpoPairingWebhook…testARevivalBetweenTheDispatchersReadAndItsHoldIsSenttestARevivalBetweenTheDispatchersReadAndItsHoldIsSenttestARevivalStartsTheFailureCountAgaintestOnlyTheLastSevenDaysAreSentAgaintestADeadWebhookHolds…testADeadWebhookHolds…,testOnlyTheLastSevenDays…,testARevivedLpo…,testPutRevives…testPutRevivesThroughTheController,testWithoutRequeueOnlyNewEventsAreSenttestAnActiveWebhookIsLeftAsItIsAndAnotherAccountsIsNotFoundtestPutRevives…testPutRevivesThroughTheControllertestPutRevivesThroughTheControllertestPutRevivesThroughTheControllertestPutRevivesThroughTheController--no-requeueLtvWebhookReviveCommandTest::testItRevivesWithTheMissedDeliveriesUnlessToldNotLtvWebhookReviveCommandTest::testABadIdIsRefusedBeforeAnyRequestrequeue: trueTestLtvWebhookReviveSendsTheRevivalLive pass
tests/live/ltv-webhook-revive.sh(new) ran on an instance from this branch: PHP 8.3.6php -S, MariaDB 10.11, port 8240. The receiver setup is #273's: a Python https receiver behind socat at an address put onlo(6.7.8.42:8443), with a CA made for the pass and trusted through-d curl.cainfo. On a78e59a, 29 passed. On 7ac4aa9, 36 passed, 0 failed, on an instance installed by the installer's own steps without its licence form (see "Licence"):failed/6, the curl error, webhookdead. Betweenltv_webhooks.phpruns the pass sets that delivery due again, which is what the backoff's minutes would do.2 held for a dead webhook, each delivery isfailed/0, andGET /ltv/webhooks/{id}/deliveriesshowsheld: …. The receiver still had only the first delivery.pendingat the revival (7ac4aa9).PUT /ltv/webhooks/{id} {"status":"active"}answeredrevived: true, requeued: 3, held: 0, with the same webhook id, the webhookactive, and no secret in the answer.php 202-cronjobs/index.phprun (the installer's line, with this minute's record removed) delivered all four. Each was received exactly once, oldest first (killer, the two held, the pending one), recordeddelivered, and signed with the webhook's original secret.pending, thenbin/p202 ltv:webhook:revive <id> --no-requeue --json:revived: true, requeued: 0, held: 1, active. The pending one isfailed/0withheld: its webhook was revived for new events only…(7ac4aa9). A new event was sent; neither the missed one nor the pending one was, and both stayfailed.The 409 ran over HTTP on 7ac4aa9: with another MySQL session holding the dispatcher's lock,
PUT /ltv/webhooks/{id}answered 409 after 20 s ("LTV webhook deliveries are being sent right now, … nothing was changed. Try again in a minute."), and the webhook stayeddead. Once the lock was released, the same PUT revived it.The LTV Settings Revive button ran separately, on a78e59a only: step 5 of the script needs
P202_PASS, and see "Licence" below. 7ac4aa9 changes nothing on that page; the button always revives withrequeuetrue. On a dead webhook with two failed deliveries in the last 7 days, 10 passed, 0 failed:action=revive_webhook, the page's token) answered "Webhook no setup menu bar in clickserver screen #3 is active again; 2 delivery(ies) it missed in the last 7 days will be sent on the next cron run.";pending/0, and the row no longer has the button;webhook_id=12abcis refused ("Pick the webhook to revive from the list.").Licence (the stub is down)
Since the 10/28 restart the licence stub is down. The made-up key is refused by the production licence service.
p202 ltv webhooks revivedid not run end to end. The API path it calls (the PUT) ran in the live pass, and the command is covered by its unit test against a recording server.api-key-required.php. For the button pass I appendedvalid_key|b:1;to that one scratch session's file, which is what the licence answer sets on a valid key. The pass then ran through the real page; no code was changed for it. The button was not clicked in a browser. What ran is the POST its script makes, with the page's own token.install.php's own steps (schema, account, REST key) run from a scratch script, without its licence check, because the installer's POST refuses the made-up key. The REST API and the PHP CLI need no licence answer.Verification
.claude/skills/p202-verify/scripts/verify.sh --since origin/1.9.79-ltv-webhooks-scheduled --changed, on 7ac4aa9's tree (and on a78e59a before it, with the same result), PHP 8.3.6, with a scratch MariaDB 10.11 database for the schema tier. The unit tier ran 6,116 tests with 5 skipped: the 4 documented "User class requires DB singleton" skips, andCronEntryPointsFailLoudlyTest's executed case, which skips while the worktree holds the live instance's202-config.php.Beyond the script, against fresh scratch MariaDB 10.11 databases, one suite each:
WebhookReviveIntegrationTestWebhookDispatchIntegrationTestLtvDatabaseIntegrationTestConversionBridgeIntegrationTestAccountAdministrationIntegrationTestshellcheck --severity=warningon the new live script: clean.Not run:
valid_key), and step 5 of the live script on 7ac4aa9;tests/run-integration-suites.sh;For the reviewer to check
failed. No schema change: a held delivery isfailedwith aheld: …reason and its attempts as they were. Apendingstatus with a parking condition would have made every dispatcher read scan past a dead webhook's backlog every minute.202_ltv_webhook_deliveries(1.9.79: the minutely cron sends LTV webhooks, within a budget #273 found that). A webhook left dead for good keeps adding held rows; only the last 7 days of them can ever be sent.disabledis unchanged. No code writes it. It keeps the oldendpoint not activepath, andrevive()accepts it likedead, but the API offers onlyactive.held:reasons. A delivery held for a dead webhook says reviving sends it. One arequeue=falserevival set aside says the webhook was revived for new events only. For the second, the webhook is already active, so the first reason's "reviving … sends it" would not be true. Both start withheld:.RUN_SECONDSplus one delivery's timeout fromindex.php, or--budgetfromltv_webhooks.php), and then answers 409. The guarantee that no claimed delivery is held or made due rests on that lock, as the dispatcher's one-run-at-a-time already does. A run that cannot see the lock (a server whereGET_LOCKis not shared) is protected only as far as the claim's compare-and-set protects it.requeue. Their attempts go back to 0, butnext_attempt_atstays, since a claim cannot be told from a backoff on the row. A row in backoff when the webhook died is sent at most 32 minutes later rather than on the next run.OutboundUrlGuard, and it changes neitherpost(),requestOptions(),headers(),create()norassertUrlAllowed(). InWebhookDispatcherit changes the class docblock,run()'s report and its not-active branch,summary()anddidWork(). 1.9.79: LTV webhooks go through the export webhooks' guard and sender #278's resolution replacespost()and dropsrequestOptions()/headers(). InMysqlWebhookRepositoryit addsHELD_REASONunderMAX_ATTEMPTS, changes one line ofenqueue()'s SELECT, and adds three methods at the end of the class. 1.9.79: LTV webhooks go through the export webhooks' guard and sender #278 changescreate(), removesassertUrlAllowed()and addsurlRefusal()/PORTS. Expect at most adjacent-line conflicts near the constants, and the changelogs.Found outside this item (not fixed here)
202-account/api-integrations.php) shows "Connected" whatever its webhook's status, so an operator is not told the optimizer has stopped receiving events.webhook_id, which the page casts with(int), so12abcdeletes webhook 12 (CLAUDE.md subid missing in pixel url #4). The new Revive action reads the id strictly.https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Generated by Claude Code