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 terms | Privacy notice | Consent | Informational | |
|---|---|---|---|---|
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.
});
How an objection interacts with deemed consent
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.