BIO.RE
Notifications

Unsubscribe Web Push

Soft-deactivate a single web push subscription by endpoint. Ownership-checked. Server keeps the row (active=false) for forensic / re-subscribe-detection.

POST /api/v1/notifications/webpush/unsubscribe — 🔑 Bearer

Soft-deactivates a single WebPushSubscription by endpoint. Ownership-checkedsubscription.userId === bearer.sub; otherwise 404 subscription_not_found (same code for missing OR cross-user — no enumeration leak). The row is NOT deleted: active is set to false so the dispatch pipeline skips it but the row stays for audit / re-subscribe detection.

Soft-delete, not hard-delete. The row stays in the DB with active: false. If the user re-subscribes from the same browser later, the existing row is reactivated (via the upsert in POST /webpush/subscribe). This avoids accumulating duplicate rows across the subscribe/unsubscribe cycle.

Browser-side cleanup is your responsibility. This endpoint only flips the server-side flag. The actual browser-level subscription stays alive until your code calls subscription.unsubscribe() on the PushSubscription object. Run both calls in sequence — server first (so dispatches stop), then browser (so the keys are released).

Request

Body — WebPushUnsubscribeDto

FieldTypeRequiredValidationNotes
endpointstringIsNotEmpty()The same subscription.endpoint URL used at subscribe time
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OK

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 200

(Not wrapped in ApiResponseOf<T> — the controller returns the bare object.)

Errors

HTTPcode / i18nKeyReason
400(DTO validation)Missing endpoint
401(guard)Missing / invalid bearer token
404notification.webpush.subscription_not_foundNo row for this endpoint OR row belongs to a different user (same code)

Side effects

  1. prisma.webPushSubscription.findUnique({ where: { endpoint } }).
  2. Missing OR userId !== caller → throw subscription_not_found.
  3. prisma.webPushSubscription.update({ where: { id }, data: { active: false } }).
  4. Audit log: [webpush] Deactivated subscription <id> for user <userId>.

Code samples

curl -X POST https://api.bio.re/api/v1/notifications/webpush/unsubscribe \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"endpoint": "https://push.example.com/abc123..."}'
async function unsubscribeWebPush(accessToken: string, endpoint: string): Promise<void> {
  const res = await fetch('https://api.bio.re/api/v1/notifications/webpush/unsubscribe', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ endpoint }),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw new Error(json?.error?.message ?? 'Web push unsubscribe failed');
  }
}
// Two-step unsubscribe: server first, then browser
async function fullyUnsubscribeWebPush(accessToken: string) {
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.getSubscription();
  if (!subscription) return; // already unsubscribed

  // 1. Tell the platform to stop dispatching
  await unsubscribeWebPush(accessToken, subscription.endpoint);

  // 2. Release the browser-side subscription
  await subscription.unsubscribe();
}

Try it

POST
/api/v1/notifications/webpush/unsubscribe

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

curl -X POST "https://loading/api/v1/notifications/webpush/unsubscribe" \  -H "Content-Type: application/json" \  -d '{}'
Empty
Empty

Source

SourcePathLines
Controllerapps/api-core/src/modules/notification/webpush.controller.ts55–66 (unsubscribe)
DTO (request)apps/api-core/src/modules/notification/webpush.controller.ts21–24 (WebPushUnsubscribeDto — inline in controller file)
Serviceapps/api-core/src/modules/notification/webpush.service.ts54–67 (unsubscribe — soft-delete, ownership-aware)
Prisma modelpackages/prisma/prisma/schema.prismaWebPushSubscription.active (soft-delete flag)

On this page