rabosh / laravel
Laravel SDK for the Rabosh platform — billing events, usage records, and consent management
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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_identifieris only needed for site-scoped calls (checkConsent,recordConsent,getDocument,publishConsentDocumentVersion). Document/version/type/webhook management works without it, but a single app with oneRABOSH_SITE_IDENTIFIERconfigured 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.
}