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-checked — subscription.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
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
endpoint | string | ✓ | IsNotEmpty() | The same subscription.endpoint URL used at subscribe time |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK
{
"success": true
}| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on 200 |
(Not wrapped in ApiResponseOf<T> — the controller returns the bare object.)
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (DTO validation) | Missing endpoint |
401 | (guard) | Missing / invalid bearer token |
404 | notification.webpush.subscription_not_found | No row for this endpoint OR row belongs to a different user (same code) |
Side effects
prisma.webPushSubscription.findUnique({ where: { endpoint } }).- Missing OR
userId !== caller→ throwsubscription_not_found. prisma.webPushSubscription.update({ where: { id }, data: { active: false } }).- 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
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 '{}'Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/notification/webpush.controller.ts | 55–66 (unsubscribe) |
| DTO (request) | apps/api-core/src/modules/notification/webpush.controller.ts | 21–24 (WebPushUnsubscribeDto — inline in controller file) |
| Service | apps/api-core/src/modules/notification/webpush.service.ts | 54–67 (unsubscribe — soft-delete, ownership-aware) |
| Prisma model | packages/prisma/prisma/schema.prisma | WebPushSubscription.active (soft-delete flag) |
Subscribe to Web Push
Register a W3C PushSubscription with the server. Endpoint-keyed upsert handles browser key regeneration AND device handover (different user on the same browser → reassign).
List Web Push Subscriptions
Read the calling user's active web push subscriptions. Returns up to 100 rows ordered newest-first. Public-safe slice — keys NOT exposed.