Search by

rabosh / laravel

yjivkov

Laravel SDK for the Rabosh platform — billing events, usage records, and consent management

1.0.0 2026-04-22 19:38 UTC

This package is auto-updated.

Last update: 2026-09-20 07:14:46 UTC


README

Laravel package for pushing billing events and usage records to the Rabosh billing platform.

Installation

composer require rabosh/laravel

Configuration

Publish the config file:

php artisan vendor:publish --tag=rabosh-config

Add to your .env:

RABOSH_API_KEY=your-api-key
RABOSH_API_SECRET=your-api-secret
RABOSH_SITE_IDENTIFIER=your-site-identifier
RABOSH_WEBHOOK_SECRET=your-webhook-secret

RABOSH_WEBHOOK_SECRET is the shared secret Rabosh uses to sign the webhooks it sends to your site. It is generated in the Rabosh dashboard when you configure a webhook endpoint for your app.

Optionally override the API URL (defaults to https://api.rabosh.com):

RABOSH_BASE_URL=https://custom-instance.example.com

Usage

Push a billing event

use Rabosh\Facades\Rabosh;

// Simple event
Rabosh::event('api.request', [
    'endpoint' => '/users',
    'method' => 'GET',
]);

// With timestamp
Rabosh::event('sms.sent', [
    'recipient' => '+1234567890',
    'segments' => 2,
], now()->toIso8601String());

Async (queued) events

Enable in .env:

RABOSH_QUEUE_ENABLED=true
RABOSH_QUEUE_CONNECTION=redis
RABOSH_QUEUE_NAME=billing
Rabosh::eventAsync('api.request', ['endpoint' => '/users']);

Push usage records

use Rabosh\Facades\Rabosh;

// Single usage record
Rabosh::usageRecord('api-calls', 1.0);

// With timestamp and metadata
Rabosh::usageRecord('storage-gb', 2.5, now()->toIso8601String(), [
    'bucket' => 'uploads',
]);

// Async (queued)
Rabosh::usageRecordAsync('api-calls', 1.0);

// Batch — send multiple records in one request
Rabosh::usageRecordBatch([
    ['metered_feature_id' => 'api-calls', 'amount' => 5],
    ['metered_feature_id' => 'storage-gb', 'amount' => 1.2, 'metadata' => ['bucket' => 'media']],
]);

Dependency injection

use Rabosh\RaboshClient;

class MyService
{
    public function __construct(private RaboshClient $rabosh) {}

    public function doWork(): void
    {
        $this->rabosh->event('work.completed', ['duration_ms' => 150]);
        $this->rabosh->usageRecord('jobs-processed', 1.0);
    }
}

Buffer & Retry (Offline Resilience)

When the Rabosh API is unreachable, the SDK can buffer failed requests locally so no billing data is lost. Buffered requests are stored as JSON files and retried later.

Enable the buffer

In .env:

RABOSH_BUFFER_ENABLED=true
RABOSH_BUFFER_PATH=   # optional, defaults to storage/rabosh/buffer

Retry buffered requests

Run manually:

php artisan rabosh:retry-buffered

Or schedule it in your app/Console/Kernel.php:

$schedule->command('rabosh:retry-buffered')->everyFiveMinutes();

Check buffer status

$pending = Rabosh::bufferCount();

Document Consent

Manage versioned legal documents (Terms & Conditions, Privacy Policy, etc.) and track user consent per version.

Check if a user needs to consent

use Rabosh\Facades\Rabosh;

// Check all documents published on this site
$result = Rabosh::checkConsent('user-123');

// Check specific documents
$result = Rabosh::checkConsent('user-123', ['terms-and-conditions', 'privacy-policy']);

// With locale preference
$result = Rabosh::checkConsent('user-123', null, 'bg');

// Response structure:
// ['data' => [
//     ['slug' => 'terms-and-conditions', 'status' => 'pending', 'enforcement' => 'blocking', ...],
//     ['slug' => 'privacy-policy', 'status' => 'consented', ...],
// ]]

Fetch a published document

// Get the document content in the user's locale
$doc = Rabosh::getDocument('terms-and-conditions', 'bg');

// $doc['title'], $doc['body'], $doc['version_label'], $doc['served_locale']

Record consent

Rabosh::recordConsent(
    externalUserId: 'user-123',
    documentSlug: 'terms-and-conditions',
    source: 'web',
    locale: 'en',
    metadata: [
        'consent_text_presented' => 'I have read and agree to the Terms and Conditions',
        'policy_url' => 'https://mysite.com/terms',
        'geo_country' => 'BG',
    ],
);

// Returns 201 on success, or empty array on failure (buffered for retry)
// Returns 409 if user already consented to this version — treat as success

Marketing Consent

Track channel-based marketing permissions (email, SMS, push, etc.) with grant/revoke lifecycle and TTL expiration.

Grant consent

use Rabosh\Facades\Rabosh;

Rabosh::grantMarketingConsent(
    externalUserId: 'user-123',
    consentTypeSlug: 'email_marketing',
    source: 'web',
    locale: 'en',
    options: [
        'ttl_days' => 365,
        'consent_text_presented' => 'I agree to receive promotional emails',
        'policy_url' => 'https://mysite.com/preferences',
    ],
);

// With idempotency key for safe retries
Rabosh::grantMarketingConsent(
    externalUserId: 'user-123',
    consentTypeSlug: 'email_marketing',
    source: 'web',
    locale: 'en',
    options: ['ttl_days' => 365],
    idempotencyKey: 'grant-user123-email-' . date('Ymd'),
);

Revoke consent

Rabosh::revokeMarketingConsent(
    externalUserId: 'user-123',
    consentTypeSlug: 'email_marketing',
    source: 'web',
    locale: 'en',
);

Check marketing consent status

// Check all consent types
$result = Rabosh::checkMarketingConsent('user-123');

// Check specific types
$result = Rabosh::checkMarketingConsent('user-123', ['email_marketing', 'sms_promotions']);

// Response structure:
// ['data' => [
//     ['slug' => 'email_marketing', 'status' => 'granted', 'expires_at' => '2027-07-22T...'],
//     ['slug' => 'sms_promotions', 'status' => 'expired', ...],
// ]]

Get consent history

// Document consent history (default)
$history = Rabosh::consentHistory('user-123');

// Explicitly document-only
$history = Rabosh::consentHistory('user-123', 'document');

// Marketing consent history
$history = Rabosh::consentHistory('user-123', 'marketing');

Consent Management

The consent flow is fully automatable from your site: create and version your legal documents, publish them, manage marketing consent types, and configure webhooks — all through the same API keys used by the end-user consent calls.

Tip: the site_identifier is only needed for site-scoped calls (checkConsent, recordConsent, getDocument, publishConsentDocumentVersion). Document/version/type/webhook management works without it, but a single app with one RABOSH_SITE_IDENTIFIER configured covers both.

Documents

// Create a document (slug auto-generated from the name when omitted)
$doc = Rabosh::createConsentDocument('Terms and Conditions', null, 'Our T&Cs');
$documentId = $doc['data']['id'];

Rabosh::listConsentDocuments();          // all documents
Rabosh::getConsentDocument($documentId); // single document
Rabosh::updateConsentDocument($documentId, ['is_active' => false]);
Rabosh::deactivateConsentDocument($documentId); // excludes it from consent checks

Versions & translations

// Add a draft version (default language required)
$version = Rabosh::createConsentDocumentVersion($documentId, '2.0', 'en');
$versionId = $version['data']['id'];

// Fill in translations for the languages you support
Rabosh::addConsentDocumentTranslation($versionId, 'en', 'Terms v2', '<p>Body</p>');
Rabosh::addConsentDocumentTranslation($versionId, 'bg', 'Условия v2', '<p>Текст</p>');

// Finalize locks the content and computes content hashes.
// A translation for the default language must exist first.
Rabosh::finalizeConsentDocumentVersion($documentId, $versionId);

// Versions are read-only after finalization (updates return VERSION_NOT_EDITABLE).
Rabosh::updateConsentDocumentTranslation($translationId, ['title' => 'Terms v2.1']);
Rabosh::deleteConsentDocumentTranslation($translationId);

Rabosh::listConsentDocumentVersions($documentId);
Rabosh::getConsentDocumentVersion($documentId, $versionId);

Publications

// Publish a finalized version to one of your sites. Supersedes any older
// publication of the same document on that site.
Rabosh::publishConsentDocumentVersion($versionId, 'store-bg-1', [
    'requires_reconsent' => true,
    'enforcement' => 'blocking',            // blocking | soft | informational
    'grace_period_days' => 7,
    'enforce_from' => '2026-10-01',        // optional, ISO date
]);

Rabosh::listConsentDocumentPublications($documentId);

// Adjust the enforcement policy of a live publication
Rabosh::updateConsentDocumentPublication($publicationId, [
    'enforcement' => 'soft',
    'grace_period_days' => 14,
]);

// Un-publish: the document is no longer served on that site
Rabosh::deleteConsentDocumentPublication($publicationId);

Marketing consent types

$type = Rabosh::createMarketingConsentType('Email Marketing', null, 'Promotional emails', 365);
$typeId = $type['data']['id'];

Rabosh::listMarketingConsentTypes();
Rabosh::getMarketingConsentType($typeId);
Rabosh::updateMarketingConsentType($typeId, ['default_ttl_days' => 30]);
Rabosh::deleteMarketingConsentType($typeId); // deactivates; records preserved

Webhook configuration

// Read the current configuration (the secret is never returned)
$config = Rabosh::getWebhookConfig(); // ['configured' => ..., 'data' => [...]]

// Create or update. On first creation the response includes the "secret" once —
// store it to verify inbound X-Signature-256 headers.
$created = Rabosh::updateWebhookConfig('https://mysite.com/webhooks/rabosh', [
    'is_active' => true,
    'events' => ['document.consent.created', 'marketing.consent.granted', 'document.published'],
]);
$secret = $created['secret'] ?? null; // only present on creation

// Rotate the secret whenever needed (returned exactly once)
$newSecret = Rabosh::regenerateWebhookSecret();

Practical Example: Consent Gate Middleware

namespace App\Http\Middleware;

use Closure;
use Rabosh\Facades\Rabosh;

class RequireConsent
{
    public function handle($request, Closure $next)
    {
        $user = $request->user();
        $result = Rabosh::checkConsent($user->external_id);

        $pending = collect($result['data'] ?? [])
            ->where('status', 'pending')
            ->where('enforcement', 'blocking');

        if ($pending->isNotEmpty()) {
            return redirect()->route('consent.show', [
                'documents' => $pending->pluck('slug')->toArray(),
            ]);
        }

        return $next($request);
    }
}

Inbound Webhooks (payment notifications)

Rabosh can notify your site when a payment for your customers is created and when it is paid (e.g. payment.created, payment.paid). The SDK ships a ready-made receiver so you only write the business logic.

The package automatically registers POST /webhooks/rabosh (configurable). Because the request is HMAC-signed, exempt the route from CSRF in your bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: ['webhooks/rabosh']);
})

Register handlers

Register handlers in a service provider's boot():

use Rabosh\Facades\Rabosh;
use Rabosh\Webhooks\Webhook;

public function boot(): void
{
    $webhooks = app('rabosh.webhooks');

    $webhooks->on('payment.created', function (Webhook $webhook) {
        $orderId = $webhook->data['order_id'] ?? null;
        // Mark the order as awaiting payment
    });

    $webhooks->on('payment.paid', function (Webhook $webhook) {
        Log::info('Payment received', $webhook->data);
        // Fulfil the order
    });

    // Wildcards and catch-all work too
    $webhooks->on('payment.*', fn (Webhook $webhook) => ...);
}

Webhook exposes eventType, eventId, timestamp, appId, and data (the payment details incl. transaction_id, payment_intent_id, invoice_id, order_id, amount, currency, status, captured_at).

Manual / custom routes

If you prefer your own route, disable auto-registration in .env:

RABOSH_WEBHOOK_ROUTE_ENABLED=false

then map Rabosh\Webhooks\WebhookController to any path you like:

use Rabosh\Webhooks\WebhookController;

Route::post('/payments/callback', WebhookController::class)
    ->name('payments.callback');

Or reuse the Rabosh\Webhooks\VerifiesWebhookSignature trait in your own controller to validate the X-Signature-256 header, then call app('rabosh.webhooks')->handle($payload).

Response & retries

The receiver responds 200 (with the event_id) on success, 401 on a bad signature, and 400/422 on malformed payloads. Rabosh retries failed deliveries, so keep handlers idempotent (key on $webhook->eventId).

Error Handling

By default, the package will never throw exceptions into your application. All failures (network errors, auth errors, server errors) are caught internally and logged via Laravel's Log facade with a [Rabosh] prefix. Your application continues to run normally.

// This is safe — if the Rabosh API is down, the call logs the error,
// buffers the request (if enabled), and returns [].
Rabosh::event('api.request', ['endpoint' => '/users']);

Queued events (eventAsync, usageRecordAsync) also handle failures gracefully. The jobs retry 3 times and log the failure if all retries are exhausted — they will never surface an unhandled exception in your application.

Checking for failures

Since event() and usageRecord() return an empty array on failure, you can detect issues:

$result = Rabosh::event('api.request', ['endpoint' => '/users']);

if (empty($result)) {
    // The event was not delivered — check your logs for details.
    // If buffer is enabled, it's stored for retry.
}