Skip to main content

Objections and terminations

Two rights need their own record, because a change can be answered without accepting it:

// A Widerspruch: rebuts a deemed-consent change (§ 308 Nr. 5 lit. a BGB) — the subject is
// then never deem-accepted — or objects to legitimate-interest processing (Art. 21 GDPR).
Consent::object($user, 'terms', ConsentContext::forMethod(ConsentMethod::SettingsToggle));

// The free right to terminate before a change takes effect (§ 675g / § 327r Abs. 3 BGB, P2B).
Consent::terminate($user, 'terms', ConsentContext::forMethod(ConsentMethod::SettingsToggle));

Which documents each one applies to

Neither right applies everywhere, and the package refuses the combinations that do not exist rather than writing them down:

Contract termsPrivacy noticeConsentInformational
Consent::withdraw()
Consent::object()
Consent::terminate()

A consent is withdrawn, not objected to — Art. 21 does not cover consent-based processing, and an objection row against a consent reads to a later auditor like a withdrawal that never happened. A privacy notice cannot be terminated, because it is information rather than a thing that ends. An informational page binds nobody, so none of the three reaches it.

A refused call throws NotWithdrawableException, NotObjectableException or NotTerminableException. The ledger is append-only: a row asserting a state that does not legally exist cannot be corrected afterwards, and every later reader takes it as proof — so a loud failure is the cheaper outcome by a wide margin.

At the HTTP boundary the refusal is translated rather than propagated, because there it is an ordinary request to answer and not a programming error: the JSON API returns 422 with an error of not_withdrawable / not_objectable / not_terminable, and the Livewire components answer 404. A document key that is not published at all is 404 unknown_document on the API and 404 on the components — see Recording consent. Calling the manager or the facade directly gets the exception, unchanged.

The one exception is the re-consent form's submit(), which does not abort: it may already have appended rows before it reaches a document that vanished, so it clears the ticks and re-renders instead of leaving the subject on an error page with no idea what took effect.

Your app has to act on them

Each call appends one immutable ledger row and fires an event — ConsentObjected or ConsentTerminated — because only your app can act on it: the package cannot know which processing an Art. 21 objection must stop, or how your contract ends. Listen and act:

Event::listen(ConsentObjected::class, function (ConsentObjected $event): void {
// Stop the objected-to processing for $event->consent->subject, where no other basis applies.
});

A subject who objects inside a deemed-consent objection window is recorded as having objected, and the fiction never applies to them: the sweep that binds silence skips anyone who answered. A subject who terminates in time is recorded the same way.

Both rights are also reachable over the headless JSON API (POST /legal/object, POST /legal/terminate) and from the shipped settings screen — see User interface.