Documentation / Post-processing and integrations
Push notifications
Release status: implementation preview. The components below have source and automated coverage, but the full subscriber, admin-send, guest lifecycle, account, and cross-browser flow has not completed fleet runtime acceptance.
FunkyCommerce stores browser subscriptions in the public theme and delivers notifications through the licensed Superfunky PRO companion. Delivery uses the Composer-locked minishlink/web-push package bundled with the PRO release.
Architecture
The intended flow contains:
- the storefront service worker at
/sw.js; - a user-triggered browser permission request;
- a validated browser
PushSubscription; - rate-limited WordPress subscription routes;
- the protected PRO delivery composer and bounded WP-Cron queue;
- service-worker display, subscription-rotation and same-origin navigation handlers.
| Method | Route | Purpose |
|---|---|---|
GET | /wp-json/funkycommerce/v1/push/vapid-public-key | Return the protected configuration's public key |
POST | /wp-json/funkycommerce/v1/push/subscribe | Validate and upsert a subscription by endpoint |
POST | /wp-json/funkycommerce/v1/push/unsubscribe | Validate and remove an endpoint |
Public writes are limited per source address. Invalid JSON, endpoint, key, disabled feature, missing configuration and throttling states return non-2xx WordPress REST errors. The storefront treats these responses as failures and rolls back a newly created local subscription when synchronization fails.
Endpoints must resolve safely over HTTPS and belong to the built-in Apple, Google, Mozilla or Microsoft browser-push hosts. A deployment supporting another audited push service can extend funkycommerce_push_allowed_endpoint_hosts; do not allow arbitrary hosts because the delivery worker makes server-side requests to these URLs.
Existing option records are migrated lazily to the canonical schema, invalid rows are discarded, endpoints are deduplicated, and only the 500 most recently refreshed subscriptions remain. Records not refreshed for 180 days are removed by daily cleanup. Expired provider responses (HTTP 404/410) are removed during delivery.
1. Configure VAPID securely
Generate one VAPID key pair for each WordPress environment. Use the Minishlink library bundled with the installed Superfunky PRO release; do not use an online generator. Run this once in a private server terminal from the WordPress root (/opt/bitnami/wordpress on a standard Bitnami image):
cd /opt/bitnami/wordpress
wp eval '$keys = \Minishlink\WebPush\VAPID::createVapidKeys(); printf("PUBLIC=%s\nPRIVATE=%s\n", $keys["publicKey"], $keys["privateKey"]);'
The command itself contains no secret, but its output does. Do not run it through a shared terminal logger, paste the output into chat or a ticket, or leave it in terminal scrollback. If the command reports that the class is unavailable, install and activate the packaged Superfunky PRO release before retrying; do not install an unrelated global Composer package.
Put all three values in protected server configuration. On a standard Bitnami image, edit /opt/bitnami/wordpress/wp-config.php with sudoedit and preserve its existing owner and mode. Add the constants before the /* That's all, stop editing! Happy publishing. */ line:
define( 'SUPERFUNKY_VAPID_SUBJECT', 'mailto:ops@example.com' );
define( 'SUPERFUNKY_VAPID_PUBLIC_KEY', 'PUBLIC_VALUE_FROM_GENERATOR' );
define( 'SUPERFUNKY_VAPID_PRIVATE_KEY', 'PRIVATE_VALUE_FROM_GENERATOR' );
The subject must be a mailto: address or URL. Never enter the private key in wp-admin, store it in a WordPress option, expose it to the storefront, commit it, or include it in support logs. Avoid putting either key directly in a wp config set command because shell history and process inspection can retain command arguments.
Verify that WordPress loaded correctly sized keys without printing either value:
wp eval '
$decode = static function ( $value ) {
$padding = str_repeat( "=", ( 4 - strlen( $value ) % 4 ) % 4 );
return base64_decode( strtr( $value, "-_", "+/" ) . $padding, true );
};
$public = defined( "SUPERFUNKY_VAPID_PUBLIC_KEY" )
? $decode( SUPERFUNKY_VAPID_PUBLIC_KEY )
: false;
$private = defined( "SUPERFUNKY_VAPID_PRIVATE_KEY" )
? $decode( SUPERFUNKY_VAPID_PRIVATE_KEY )
: false;
printf(
"subject_configured=%s\npublic_bytes=%d\nprivate_bytes=%d\n",
defined( "SUPERFUNKY_VAPID_SUBJECT" ) && SUPERFUNKY_VAPID_SUBJECT !== "" ? "yes" : "no",
is_string( $public ) ? strlen( $public ) : 0,
is_string( $private ) ? strlen( $private ) : 0
);
'
The expected output is subject_configured=yes, public_bytes=65, and private_bytes=32. This checks shape, not ownership or delivery. The wp-admin Web Push screen must also report that protected configuration is ready.
Rotate the pair only as a coordinated migration: changing the public key makes existing browser subscriptions unusable and users must opt in again. Keep the previous value out of backups or logs that do not already have secret-grade access controls.
Activate a valid Superfunky PRO licence and enable Push notifications in Appearance > FunkyCommerce > Push. The public-key route returns 503 while the feature or protected sender configuration is unavailable.
The storefront derives every Push REST endpoint from VITE_GRAPHQL_ENDPOINT through the shared backend-origin helper. Do not configure separate subscribe/unsubscribe URLs. VITE_VAPID_PUBLIC_KEY remains an optional public-key build-time override; omitting it keeps key rotation server-side.
2. Send a test or broadcast
Open Appearance > Web Push. The page reports configuration readiness, active subscription count and queued campaign count without displaying endpoints or keys.
- Enter a title (80 characters maximum) and message (180 maximum).
- Use a root-relative destination such as
/account/orders, or an HTTPS URL on the
configured storefront host.
- Optionally set a collapse tag.
- Send a test to the most recently refreshed subscription.
- After verifying the device, queue a broadcast.
WP-Cron processes at most 25 subscriptions per pass and schedules another pass when work remains. The 20 retained logs contain only campaign type, timestamps and aggregate total/sent/failed/cleaned-up counts. They intentionally omit payloads, endpoints, keys and provider response bodies.
WP-Cron must run reliably in production. If DISABLE_WP_CRON is enabled, invoke wp-cron.php from the platform scheduler at least once per minute.
3. Browser enablement
Permission must follow an explicit user action. Explain what will be sent, expected frequency, retention, and how to disable notifications. Push permission is separate from analytics and email consent.
- iPhone/iPad: Web Push requires a supported iOS/iPadOS release and a Home Screen
web app. Open the site in Safari, choose Share > Add to Home Screen, launch the installed app, then enable notifications. Other iOS browsers cannot bypass this installation requirement.
- Android: use a current Chrome, Edge, Firefox, or Samsung Internet version and
allow notifications in the browser's site settings. Battery optimization may delay delivery.
- Desktop Safari: use a current macOS/Safari release and allow the site in
Safari's notification settings.
- Chrome, Edge, Firefox: check the site's permission panel after a denial; browsers
generally stop showing repeated prompts.
The storefront helper exposes browser-specific guidance from src/lib/push.ts for unsupported mobile flows.
Payload and navigation safety
{
"title": "Your order has shipped",
"body": "Open the store to see tracking details.",
"tag": "order-update",
"url": "/account/orders"
}
Destinations are normalized twice: the sender accepts root-relative or configured-host URLs, and the service worker rejects cross-origin notification targets. An existing storefront window receives a navigation message; otherwise the worker opens the safe same-origin URL. Do not put private order details, credentials, tokens or untrusted HTML in a notification.
Production verification
- Install the required FunkyCommerce Headless and Superfunky PRO versions, activate the
PRO licence, and complete the protected VAPID configuration above.
- Build with an explicit
VITE_GRAPHQL_ENDPOINTand deploy over HTTPS. - Confirm
/sw.jsis served from the storefront origin with root scope. - Confirm
/wp-json/funkycommerce/v1/push/vapid-public-keyreturns HTTP200and only
the configured public key.
- Opt in from a user gesture and verify the subscribe request returns
201. - Confirm Appearance > Web Push shows the active subscription, then send an admin
test and inspect only the summarised result.
- Test foreground, background, closed-browser, click, and unsubscribe behavior.
- Test supported Chrome/Edge, Firefox, Android and installed iOS Safari flows.
- Disable Push and confirm new public-key/subscription requests fail closed.
- Verify WP-Cron drains large broadcasts in bounded batches and expired endpoints are
cleaned up.
- Test key rotation only as a planned migration and confirm old subscriptions must
opt in again.
Next: harden the deployment.