Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Nostr Laravel Package

revolution/laravel-nostr

Laravel package for working with Nostr: generate and manage keypairs, create and sign events, publish to relays, verify signatures, and handle NIP-05 identifiers. Provides facades and configuration for adding decentralized social features to Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the config.

composer require revolution/laravel-nostr
php artisan vendor:publish --tag=nostr-config

The package is a thin Laravel wrapper around a PHP Nostr client. It auto-registers a Nostr facade and ships a single config file, so there is very little surface area to learn.

Configure keys and relays. Everything flows through two env values:

NOSTR_SECRET_KEY=64_hex_chars
NOSTR_PUBLIC_KEY=64_hex_chars   # derived from secret if omitted
NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol

config/nostr.php maps these into a relay array plus a default driver. Clear/publish config before testing, and remember php artisan config:cache if you run it in production.

First use case — publish a text note. This is the "hello world" and the fastest way to confirm signing, relay connectivity, and key parsing all work end to end:

use Revolution\Nostr\Facades\Nostr;

$event = Nostr::event()->create(content: 'Hello from Laravel');
$result = Nostr::event()->publish($event);

Where to look first. Read vendor/revolution/laravel-nostr/src/ in this order: the facade (it lists every driver method), the service provider (what gets bound), then config/nostr.php. The package is small enough to read in ten minutes — do that before trusting the README, because the driver method names are the real API contract.

Implementation Patterns

Treat the facade as a driver registry, not a client. Methods like Nostr::event(), Nostr::text(), Nostr::relay(), and Nostr::directMessage() each return a handler scoped to one NIP. Build an event with the handler, then hand it to publish(). Keep those two steps separate — the intermediate event object is what you persist, log, and retry.

Key management is the real design decision. For single-tenant apps, the env secret is fine. For multi-user apps, store a per-user keypair encrypted in the database (encrypted cast) and resolve it per request rather than relying on the global config. Never let the secret key leave the server; the public key is the only thing that should ever hit a frontend.

Publish through a queued job. Relay round-trips are slow and flaky. Wrap publishing in a ShouldQueue job with $tries = 3 and a backoff; the job holds the signed event array, not the content, so retries are idempotent (event IDs are deterministic).

class PublishNote implements ShouldQueue
{
    use Queueable, SerializesModels;
    public int $tries = 3;
    public array $backoff = [5, 30, 120];

    public function __construct(public array $event) {}

    public function handle(): void
    {
        $results = Nostr::event()->publish($this->event);
        // log per-relay results; a 200 does not mean the relay stored it
    }
}

Fan out to multiple relays, tolerate partial failure. Most implementations return a per-relay response map. Iterate it, log failures, and treat "at least one relay accepted" as success. Do not throw on a single relay timeout — that's normal in Nostr.

Reading: filters, not queries. Subscribe/fetch calls take NIP-01 filters (kinds, authors, since, until, limit, #e, #p tags). Build a small repository class around these so your controllers and Livewire components never construct raw filter arrays. Store ingested events in a local events table keyed by the event ID (id is a sha256 hash — a natural primary key and free idempotency).

A custom Notification channel is the cleanest integration. If you're adding Nostr to an existing Laravel app, a NostrChannel that maps toNostr() on the notification onto a kind-1 event keeps the rest of the app ignorant of the protocol. Same pattern works for an Event/Listener pair on domain events.

Testing. Fake the relay layer with Http::fake() if the driver is HTTP-based; otherwise bind a stub implementation into the container in your test case. Freeze time with Carbon::setTestNow() — event timestamps and filter windows are second-precision and tests will drift otherwise.

Gotchas and Tips

  • Hex vs. bech32. The package almost certainly expects a 64-character hex secret key, not the nsec1… form users export from Damus/Amethyst. Store the hex form server-side and convert at the boundary if you accept user input. A mismatched format fails at signing time, not at config time — the error is easy to miss.
  • Relay URLs are fussy. Use wss:// in production, no trailing slash, no query string. A relay that accepts on one path prefix and rejects on another will look like a signature bug.
  • Timestamps are seconds, not milliseconds. The package handles this, but any manual filter building you do must match. Off-by-1000 silently returns zero events rather than an error.
  • Event IDs are order-sensitive. The ID is a sha256 over a serialized array; hand-editing an event array after signing invalidates it. Always re-create rather than patch.
  • Verify on read, not just trust on write. Signatures can be forged or mangled in transit. Run verify() on ingested events before persisting; it's cheap and it's the whole security model.
  • NIP-04 DMs are legacy. If the package exposes directMessage() via NIP-04, treat it as deprecated and check whether NIP-44 is available. Don't build new features on the old path.
  • Long-lived subscriptions do not belong in a request cycle. A controller that opens a subscription will hit max_execution_time. Use an Artisan command under Supervisor, or a horizon queue worker with a long timeout, and reconnect with exponential backoff on close.
  • Config caching bites on relay arrays. If you parse a comma-separated env into an array inside a closure, config:cache will serialize the result once and ignore env changes on deploy. Prefer explicit explode in the config file at load time.
  • Relay rate limits are silent. Several popular relays throttle or drop low-power accounts without returning an error code. Add application-level throttling and log the raw per-relay response so you can tell "rejected" from "ignored".
  • Pin the version. This package has a single-digit star count and zero dependents. There is no ecosystem pressure to keep interfaces stable. Use an exact version constraint ("1.x.y") and vendor the source if it becomes load-bearing. Read the diff before every minor upgrade.
  • Extension points are the constructor bindings. Overriding Nostr::extend() (or rebinding the driver contract in a service provider) is the supported way to swap the underlying client or add a relay pool implementation — go there rather than subclassing the facade.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums