Skip to content

ESPToolKit/esp-webPush

Repository files navigation

ESPWebPush

ESPWebPush is an async-first Web Push sender for ESP32 firmware. It handles VAPID JWT signing, RFC 8291 aes128gcm payload encryption, and HTTP delivery so devices can notify browsers without custom glue code.

ArduinoJson v7+ is required for the structured payload API.

CI / Release / License

CI Release License: MIT

Features

  • RFC 8292 VAPID JWT signing with mailto: or https:// subjects.
  • Accepts standard unpadded base64url VAPID keys generated by Node/web-push tooling.
  • RFC 8291 / RFC 8188 aes128gcm Web Push encryption.
  • Async queue + worker task via native FreeRTOS APIs.
  • Bounded shutdown via requestStop(), join(timeoutMs), and deinit(timeoutMs).
  • Sync send() API plus async send() overloads that return WebPushEnqueueResult.
  • Strict PushPayload validation for browser notification fields.
  • Payload-size guard with the RFC-safe default limit of 3993 bytes.
  • Small per-origin JWT cache to avoid re-signing every message.
  • Configurable queue length, memory caps, retries, timeouts, and worker task settings.
  • Optional TLS transport controls for certificate bundle, global CA store, and common-name verification behavior.

Quick Start

#include <Arduino.h>
#include <ESPWebPush.h>

ESPWebPush webPush;

void setup() {
    Serial.begin(115200);

    WebPushVapidConfig vapid;
    vapid.subject = "mailto:notify@example.com";
    vapid.publicKeyBase64 = "BAvapidPublicKeyBase64Url...";
    vapid.privateKeyBase64 = "vapidPrivateKeyBase64Url...";

    WebPushConfig cfg;
    cfg.queueLength = 16;
    cfg.queueMemory = WebPushQueueMemory::Psram;
    cfg.worker.stackSizeBytes = 16 * 1024;
    cfg.worker.priority = 3;
    cfg.worker.name = "webpush";
    cfg.maxPayloadBytes = 3993;
    cfg.networkValidator = []() { return true; };

    webPush.init(vapid, cfg);
}

void loop() {}

Usage

WebPushSubscription / Structured Payload

WebPushSubscription subscription;
subscription.endpoint = "https://fcm.googleapis.com/fcm/send/...";
subscription.p256dh = "BME...";
subscription.auth = "nsa...";

PushPayload payload;
payload.title = "Hello";
payload.body = "ESP32";
payload.tag = "demo";
payload.icon = "https://example.com/icon.png";

Async Send

WebPushEnqueueResult enqueue = webPush.send(subscription, payload, [](WebPushResult result) {
    if (!result.ok()) {
        ESP_LOGE("WEBPUSH", "Push failed: %s (status %d)",
                 result.message, result.statusCode);
        return;
    }
    ESP_LOGI("WEBPUSH", "Push OK (status %d)", result.statusCode);
});

if (!enqueue.queued()) {
    ESP_LOGW("WEBPUSH", "Enqueue failed: %s", enqueue.message);
}

Async preflight failures are returned through WebPushEnqueueResult. The callback only runs for messages that were actually queued.

ArduinoJson v7+ Send

JsonDocument doc;
doc["title"] = "Hello";
doc["body"] = "ESP32";
doc["tag"] = "demo";

WebPushResult result = webPush.send(subscription, doc);

Sync Send

WebPushResult result = webPush.send(subscription, payload);
if (!result.ok()) {
    ESP_LOGW("WEBPUSH", "Sync push failed: %s", result.message);
}

Legacy Raw JSON Payload

PushMessage msg;
msg.subscription = subscription;
msg.payload = "{\"title\":\"Hello\",\"body\":\"ESP32\"}";

// Raw payload strings remain supported, but they are not schema-validated.
WebPushResult result = webPush.send(msg);

Teardown

if (webPush.isInitialized()) {
    WebPushJoinStatus stopStatus = webPush.deinit();
    if (stopStatus == WebPushJoinStatus::Timeout) {
        ESP_LOGW("WEBPUSH", "Worker did not stop within the timeout");
    }
}

requestStop() marks shutdown and wakes the worker without blocking. join(timeoutMs) waits for the worker to exit and finalizes shutdown when the stop completes in time. deinit(timeoutMs) is the convenience wrapper that performs both in one call.

Configuration

WebPushConfig lets you tune the worker and queue:

  • queueLength - number of queued messages.
  • queueMemory - Internal, Psram, or Any.
  • worker - stack size, priority, core id, and task name.
  • requestTimeoutMs - HTTP timeout.
  • ttlSeconds - Web Push TTL header.
  • maxRetries, retryBaseDelayMs, retryMaxDelayMs - retry/backoff controls.
  • maxPayloadBytes - plaintext payload size guard. The default is 3993 bytes; use 0 to disable.
  • useTlsCertBundle - attach esp_crt_bundle_attach when the build provides the ESP x509 bundle.
  • useGlobalCaStore - forward use_global_ca_store to esp_http_client.
  • skipTlsCommonNameCheck - forward skip_cert_common_name_check to esp_http_client.
  • networkValidator - optional callback for application-defined network readiness checks.

The default transport behavior is certificate-bundle-backed (useTlsCertBundle = true), which matches common browser push providers such as FCM without extra app wiring.

Gotchas

  • System time is required for VAPID JWT expiration.
  • Web Push endpoints require TLS-capable esp_http_client.
  • Only aes128gcm is generated. Legacy aesgcm is intentionally not supported in v2.
  • subject must start with mailto: or https://.
  • VAPID keys should be standard base64url strings without PEM wrapping; unpadded Node/web-push output is supported.
  • The configured VAPID public key must match the private key.

API Reference

  • bool init(const WebPushVapidConfig&, const WebPushConfig& = {})
  • WebPushEnqueueResult send(const PushMessage&, WebPushResultCB cb)
  • WebPushResult send(const PushMessage&)
  • WebPushEnqueueResult send(const WebPushSubscription&, const PushPayload&, WebPushResultCB cb)
  • WebPushResult send(const WebPushSubscription&, const PushPayload&)
  • WebPushEnqueueResult send(const WebPushSubscription&, const JsonDocument&, WebPushResultCB cb)
  • WebPushResult send(const WebPushSubscription&, const JsonDocument&)
  • WebPushEnqueueResult send(const WebPushSubscription&, JsonVariantConst, WebPushResultCB cb)
  • WebPushResult send(const WebPushSubscription&, JsonVariantConst)
  • void requestStop()
  • WebPushJoinStatus join(uint32_t timeoutMs)
  • void setNetworkValidator(WebPushNetworkValidator)
  • WebPushJoinStatus deinit(uint32_t timeoutMs = 10000) / bool isInitialized() const
  • const char *errorToString(WebPushError)

Compatibility

  • ESP32-class targets only.
  • Arduino and ESP-IDF frameworks are supported.
  • Requires C++17, ArduinoJson v7+, and mbedTLS.
  • Do not call from ISR context.

Tests

  • On-device Unity tests live in test/test_esp_webPush.
  • CI builds Arduino examples and includes an ESP-IDF compile smoke build.

Formatting Baseline

This repository follows the firmware formatting baseline from esptoolkit-template:

  • .clang-format is the source of truth for C/C++/INO layout.
  • .editorconfig enforces tabs (tab_width = 4), LF endings, and final newline.
  • Format tracked firmware sources with bash scripts/format_cpp.sh.

License

MIT - see LICENSE.md.

ESPToolKit

About

An async-first Web Push sender for ESP32

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages