BLA
BLA (Blind LLM Agents) is the open-source Blue Defense MCP and CLI built to protect software artifacts against automated AI reverse-engineering workflows.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Official Laravel package for seamless ProofAge API integration
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
ProofAge Laravel Client — Age Verification for Laravel
A Laravel package for integrating with the ProofAge API, featuring automatic HMAC authentication and a fluent interface.
ProofAge is an online age verification platform enabling websites to confirm users meet minimum age requirements through a hosted, privacy-focused KYC process — without server-side document handling. It supports alcohol/tobacco/cannabis commerce, adult content platforms, gambling sites, and age-restricted subscriptions.檢視完整 README ↗Platform: https://proofage.xyz | Packagist: https://packagist.org/packages/proofage/laravel-client
A Laravel package for integrating with the ProofAge API, featuring automatic HMAC authentication and a fluent interface.
Full API reference: https://docs.proofage.net/api-reference
ProofAge is an online age verification platform enabling websites to confirm users meet minimum age requirements through a hosted, privacy-focused KYC process — without server-side document handling. It supports alcohol/tobacco/cannabis commerce, adult content platforms, gambling sites, and age-restricted subscriptions.
This package provides a first-class Laravel integration: a service provider with auto-discovery, a facade, HMAC-signed webhook middleware, and a setup verification command.
It is built on proofage/php-sdk, the framework-neutral ProofAge client, which does the request signing, the retries and the resource calls. This package adds the Laravel wiring and sends every request through the Http facade, so Http::fake() intercepts the client in your tests. Upgrading from 0.6? See UPGRADE.md.
Install the package via Composer:
composer require proofage/laravel-client
Publish the configuration file:
php artisan vendor:publish --provider="ProofAge\Laravel\ProofAgeServiceProvider" --tag="config"
Configure your environment variables:
PROOFAGE_API_KEY=your-api-key
PROOFAGE_SECRET_KEY=your-secret-key
PROOFAGE_BASE_URL=https://api.proofage.net
PROOFAGE_VERSION=v1
After configuration, verify your setup using the built-in command:
php artisan proofage:verify-setup
When everything is configured correctly, you should see:
✅ Configuration is valid
✅ Workspace connection successful
✅ Webhook URL is configured https://yoursite.com/api/webhooks/proofage
✅ Webhook route found: POST api/webhooks/proofage -> App\Http\Controllers\ProofAgeWebhookController@handle
✅ Webhook route is protected with VerifyWebhookSignature middleware
✅ ProofAge setup verified successfully!
The verification command ensures:
If you see errors about missing middleware, add it to your webhook route:
Route::post('/webhooks/proofage', [ProofAgeWebhookController::class, 'handle'])
->middleware('proofage.verify_webhook');
use ProofAge\Laravel\Facades\ProofAge;
use ProofAge\Laravel\Resources\VerificationResource;
// Get workspace information
$workspace = ProofAge::workspace()->get();
// Create a verification
$verification = ProofAge::verifications()->create([
// Where the person's browser is sent after the flow: a page of your app, not a webhook.
// Decisions are POSTed to the workspace's webhook URL, set in the ProofAge console
// (see Webhook Security below).
'callback_url' => 'https://your-app.com/verification/done',
// Your identifiers, echoed back in every response and webhook. `metadata` is also accepted,
// but it is stored internally and never returned, so it cannot be used for correlation.
'external_id' => (string) $user->id,
'external_metadata' => ['plan' => 'pro'],
]);
// Send the person to the hosted verification flow at $verification['url'],
// e.g. redirect()->away($verification['url']); the decision arrives by webhook.
// Get verification details
$verification = ProofAge::verifications()->find('verification-id');
// Get age estimation details
$estimation = ProofAge::verifications('verification-id')->estimation();
// [
// 'verification_id' => '...',
// 'attempt_id' => '...',
// 'age_threshold' => [
// 'minimum' => 18,
// 'passed' => true,
// 'confidence' => 0.98,
// ],
// 'gender' => [
// 'value' => VerificationResource::GENDER_FEMALE, // 0 = female, 1 = male
// 'confidence' => 0.93,
// ],
// ]
If you capture the images yourself instead of using the hosted flow:
// Accept consent: the id and text_sha256 must be exactly those of the active consent version,
// whose text (at $consent['url']) is what the person was shown. Anything else is rejected.
$consent = ProofAge::workspace()->getConsent();
ProofAge::verifications('verification-id')->acceptConsent([
'consent_version_id' => $consent['id'],
'text_sha256' => $consent['text_sha256'],
]);
// Upload media: images only. `type` is selfie or document; a document also
// needs `side` (front|back) and `document` (id|driver_license|passport|residence_permit).
ProofAge::verifications('verification-id')->uploadMedia([
'type' => 'selfie',
'file' => $request->file('selfie'),
]);
ProofAge::verifications('verification-id')->uploadMedia([
'type' => 'document',
'side' => 'front',
'document' => 'passport',
'file' => $request->file('passport_front'),
]);
// Submit verification; the decision arrives by webhook
ProofAge::verifications('verification-id')->submit();
uploadMedia() and submit() return null: the API answers both with an empty 200. A failure
throws (see Error Handling).
use ProofAge\Laravel\ProofAgeClient;
$client = app(ProofAgeClient::class); // app(\ProofAge\Sdk\Client::class) resolves the same singleton
$workspace = $client->workspace()->get();
// Lower level: makeRequest() returns a ProofAge\Sdk\Http\Response
$response = $client->makeRequest('GET', 'workspace');
$response->status();
$response->json();
ProofAgeClient is the SDK client, so its middleware and events are available: a middleware runs once per HTTP attempt, before signing; events observe the signed request and the response, with the API key and signature masked.
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use ProofAge\Laravel\ProofAgeClient;
use ProofAge\Sdk\Events\ResponseEvent;
use ProofAge\Sdk\Http\Request;
use ProofAge\Sdk\Http\Response;
$client = app(ProofAgeClient::class);
$client->pushMiddleware(fn (Request $request, callable $next): Response => $next(
$request->withHeader('X-Request-Id', (string) Str::uuid())
));
$client->onResponse(fn (ResponseEvent $e) => Log::info('proofage.response', [
'status' => $e->status(),
'attempt' => $e->attempt(),
'ms' => $e->durationMs(),
]));
See the SDK's README for the full middleware and event API and for what raw() on an event exposes.
Every request carries X-ProofAge-Sdk: laravel/{ProofAge::VERSION} php/{SDK version} (for example
laravel/0.9.0 php/0.3.0) and, unless a middleware sets its own, User-Agent: ProofAge-Laravel/0.9.0 ProofAge-PHP/0.3.0 (PHP 8.4.1). Neither is signed, and no middleware can remove this package's or
the SDK's token. A package that wraps this one puts its own first by passing the SDK options
sdk_tokens (['acme-shop/2.1.0']) and user_agent_prefix ('AcmeShop/2.1.0') to
new ProofAgeClient($config).
dd(), dump(), print_r() and var_dump() of the client, of a request or of a caught exception
show the SDK's redacted view: the secret key as [redacted], the API key and the HMAC signature
masked, a request body as its size and sha256 rather than its bytes. The SDK covers print_r() and
var_dump() itself through __debugInfo(); this package registers casters with Symfony's
VarDumper — what Laravel's dd() and dump() use, and which otherwise reads the real properties by
reflection — for the same classes when Composer's autoloader loads. var_export() and reflection
are not covered.
The package includes middleware to verify HMAC signatures on incoming webhook requests from ProofAge.
A workspace has exactly one webhook URL, set in its settings in the ProofAge console, and ProofAge POSTs every decision to it: one route per workspace.
Register the route in routes/api.php. Its routes carry no CSRF check — ProofAge's POST has no
CSRF token — and are prefixed with /api, so the URL to enter in the console is
https://your-app.com/api/webhooks/proofage. (No routes/api.php yet? php artisan install:api
creates it.)
// routes/api.php
Route::post('/webhooks/proofage', [ProofAgeWebhookController::class, 'handle'])
->middleware('proofage.verify_webhook');
In routes/web.php instead, exclude the path from CSRF verification in bootstrap/app.php, or
every webhook is rejected with a 419 before the middleware runs:
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: ['webhooks/proofage']);
})
Sent when a verification's status becomes approved, declined, resubmission_requested,
review, abandoned or expired:
{
"verification_id": "019d...",
"event": "status.updated",
"status": "declined",
"external_id": "123",
"external_metadata": {"plan": "pro"},
"reason": "document.face.mismatch",
"timestamp": "2026-09-27T12:00:00+00:00"
}
reason is set on declined and resubmission_requested only. duplicate_detected,
duplicate_count and duplicate_of (verification_id, external_id) are added when the face
matched another account, and fingerprint_signals and manual_moderation when they apply. Each
delivery carries an X-ProofAge-Webhook-Delivery-Id header that stays the same across retries of
that delivery: use it to process a delivery once. Every body also has event: status.updated for a
decision, data.updated when a tenant corrected document fields the reader got wrong (status is then the
current one, unchanged, document holds the corrected values and changed_fields names what changed).
Read event before status; a body without it is status.updated. The repository's
examples/webhook-controller.php handles data.updated first and then each status; the SDK's AGENTS.md has the full body.
The middleware (ProofAge\Laravel\Middleware\VerifyWebhookSignature), in this order:
X-HMAC-Signature, X-Timestamp and X-Auth-Client headers (401
MISSING_SIGNATURE, MISSING_TIMESTAMP, MISSING_AUTH_CLIENT).api_key and secret_key under its config prefix (proofage by default, or the one
named as proofage.verify_webhook:{prefix}); either missing is a 418 CONFIGURATION_ERROR.X-Auth-Client equals the configured api_key (401 INVALID_AUTH_CLIENT).X-Timestamp more than webhook_tolerance seconds (default 300) from now (401
TIMESTAMP_TOO_OLD).hex(hmac_sha256(X-Timestamp . '.' . rawBody, secret_key)) and compares it with
X-HMAC-Signature using hash_equals(); if that fails, it retries once over the body
re-encoded as ProofAge encodes JSON, for a proxy that re-serialised it (401 INVALID_SIGNATURE).ProofAge signs webhooks with the workspace's active secret key only (API calls accept any of
its keys), so secret_key must be the active one.
Some applications need separate verification flows for different user roles. For example, a marketplace where buyers go through a basic age check while sellers require full identity verification -- each with its own ProofAge workspace, credentials, and webhook endpoint.
The package supports this out of the box. All shared settings (base_url, version, timeout, etc.) are inherited from the default proofage config, so additional workspaces only need their own api_key and secret_key.
The default workspace (buyers) is configured via config/proofage.php as usual. For sellers, add a second set of credentials anywhere in your application config -- config/services.php is a common choice:
// config/services.php
'proofage_seller' => [
'api_key' => env('PROOFAGE_SELLER_API_KEY'),
'secret_key' => env('PROOFAGE_SELLER_SECRET_KEY'),
],
# .env
# Buyer workspace (default)
PROOFAGE_API_KEY=pk_live_...
PROOFAGE_SECRET_KEY=sk_live_...
# Seller workspace
PROOFAGE_SELLER_API_KEY=pk_live_...
PROOFAGE_SELLER_SECRET_KEY=sk_live_...
The ProofAge facade and app(ProofAgeClient::class) singleton always use the default (buyer) workspace. For the seller workspace, use ProofAgeClientFactory:
use ProofAge\Laravel\Facades\ProofAge;
use ProofAge\Laravel\ProofAgeClientFactory;
// Buyer verification -- uses default proofage.* config
$buyerVerification = ProofAge::verifications()->create([
'callback_url' => 'https://marketplace.com/buyer/verification/done', // browser return page
'external_id' => (string) $buyer->id,
]);
// Seller verification -- uses services.proofage_seller config
$sellerClient = app(ProofAgeClientFactory::class)->make('services.proofage_seller');
$sellerVerification = $sellerClient->verifications()->create([
'callback_url' => 'https://marketplace.com/seller/verification/done', // browser return page
'external_id' => (string) $seller->id,
]);
Each workspace sends webhooks to the webhook URL in its own console settings, signed with its own active secret key. Use the middleware's config prefix parameter to verify signatures with the correct credentials:
// routes/api.php
// Buyer webhooks -- verified with default proofage.* keys
Route::post('/webhooks/proofage', [BuyerWebhookController::class, 'handle'])
->middleware('proofage.verify_webhook');
// Seller webhooks -- verified with services.proofage_seller keys
Route::post('/webhooks/proofage-seller', [SellerWebhookController::class, 'handle'])
->middleware('proofage.verify_webhook:services.proofage_seller');
# Check the buyer (default) workspace
php artisan proofage:verify-setup
# Check the seller workspace
php artisan proofage:verify-setup --config=services.proofage_seller
The command checks configuration, API connectivity, webhook route existence, and that the middleware uses the matching config prefix -- so you'll be warned if the keys would mismatch.
When a custom config prefix is used, the following resolution rules apply:
| Key | Resolution |
|---|---|
api_key | Read from the specified prefix (required) |
secret_key | Read from the specified prefix (required) |
base_url | Specified prefix, falls back to proofage.base_url |
version | Specified prefix, falls back to proofage.version |
timeout | Specified prefix, falls back to proofage.timeout |
retry_attempts | Specified prefix, falls back to proofage.retry_attempts |
retry_delay | Specified prefix, falls back to proofage.retry_delay |
download_retry_attempts | Specified prefix, falls back to proofage.download_retry_attempts |
webhook_tolerance | Specified prefix, falls back to proofage.webhook_tolerance (default: 300s) |
Additional workspaces only need api_key and secret_key. If a workspace connects to a different ProofAge environment (e.g. staging), add base_url under the same prefix and it will take priority over the default.
workspace()->get() - Get workspace informationworkspace()->getConsent() - Get consent informationverifications()->create(array $data) - Create a new verificationverifications()->find(string $id) - Get verification by IDverifications(string $id)->get() - Get the verification the resource was built forverifications(string $id)->acceptConsent(array $data) - Accept consentverifications(string $id)->uploadMedia(array $data) - Upload a media file (returns null)verifications(string $id)->submit() - Submit verification for processing (returns null)verifications(string $id)->document() - Get sanitized document fields and source mediaverifications(string $id)->downloadMedia(string $mediaId) - Download one media file as a PSR-7 streamverifications(string $id)->downloadMediaTo(string $mediaId, string $path) - Download one media file straight to diskverifications(string $id)->estimation() - Get age-threshold and gender estimationverifications(string $id)->blockFace(?array $data) - Block the verification face for AMLEvery method's exact request and response shape is documented in the SDK: its AGENTS.md
(vendor/proofage/php-sdk/AGENTS.md), the @param/@return PHPDoc on ProofAge\Sdk\Resources\*,
and the bundled vendor/proofage/php-sdk/resources/openapi.json.
ProofAge\Sdk\Enums\VerificationStatus, ProofAge\Sdk\Enums\WebhookReason and
ProofAge\Sdk\Enums\BlockFaceReasonCode model the status, AML reason and reason_code values.
(ProofAge\Laravel\Enums\* was removed in 0.7.0.)
A verification's status can also be documents_required, taken from its latest attempt
(VerificationStatus::DOCUMENTS_REQUIRED since proofage/php-sdk 0.2.0). Map status with
VerificationStatus::tryFrom() rather than from() and handle null, so a status added later
does not throw. WebhookReason covers only the AML blocklist codes; treat reason as
an open string.
Inside a Laravel application, catch the Laravel name for a specific status and the SDK base class for everything:
use ProofAge\Laravel\Exceptions\AuthenticationException; // 401
use ProofAge\Laravel\Exceptions\ValidationException; // 422, getErrors()
use ProofAge\Sdk\Exceptions\TransportException; // connection refused, DNS, TLS, timeout
use ProofAge\Sdk\Exceptions\ProofAgeException; // every other non-2xx, and the base class of all of the above
try {
$verification = ProofAge::verifications()->create($data);
} catch (AuthenticationException $e) {
// 401: $e->getErrorCode()
} catch (ValidationException $e) {
// 422: $e->getErrors()
} catch (TransportException $e) {
// The API could not be reached; $e->getResponse() is null
} catch (ProofAgeException $e) {
// Everything else: $e->getCode() is the HTTP status, $e->getResponse() the ProofAge\Sdk\Http\Response
}
The client throws ProofAge\Laravel\Exceptions\AuthenticationException for a 401,
ValidationException for a 422 and ProofAgeException for every other non-2xx; the webhook
middleware throws WebhookVerificationException. All four descend from
ProofAge\Laravel\Exceptions\ProofAgeException, which descends from
ProofAge\Sdk\Exceptions\ProofAgeException — the catch-all, and the only one of the two bases that
also catches TransportException.
They do not descend from the SDK's own ProofAge\Sdk\Exceptions\AuthenticationException,
ValidationException or WebhookVerificationException: PHP allows one parent, and keeping the
pre-0.7 catch (ProofAge\Laravel\Exceptions\ProofAgeException) working won. A catch on one of
those three SDK names therefore never matches inside a Laravel application — a 422 would fall
through to whatever comes next. The Laravel names are deprecated in 0.7 and removed in 1.0, when
the SDK names become what is thrown; see UPGRADE.md.
The webhook middleware throws ProofAge\Laravel\Exceptions\WebhookVerificationException on invalid requests. It descends from ProofAge\Laravel\Exceptions\ProofAgeException (not from the SDK's WebhookVerificationException; see Error Handling above) and carries errorCode, statusCode and toArray(). By default, the exception renders a JSON error response:
{
"error": {
"code": "INVALID_SIGNATURE",
"message": "HMAC signature is invalid"
}
}
To customize this response, register a renderable in bootstrap/app.php:
use ProofAge\Laravel\Exceptions\WebhookVerificationException;
->withExceptions(function (Exceptions $exceptions) {
$exceptions->renderable(function (WebhookVerificationException $e) {
return response()->json([
'error' => [
'code' => $e->errorCode,
'message' => $e->getMessage(),
],
], $e->statusCode);
});
})
composer test
In your own application's tests, Http::fake() intercepts every request the client makes, including
multipart uploads ($request->hasFile('file')) and the signed headers ($request->header('X-HMAC-Signature')):
Http::fake(['api.proofage.net/v1/workspace' => Http::response(['id' => 'ws_1', 'name' => 'Acme'])]);
ProofAge::workspace()->get();
Http::assertSent(fn ($request) => $request->hasHeader('X-API-Key'));
If you would rather not go through the facade, ProofAge\Sdk\Testing\FakeHttpClient is a transport
double the SDK ships: new ProofAgeClient($config, $fake).
@proofage/node on npm| Platform | Repository | Use-case |
|---|---|---|
| Node.js | ProofAge/node-client | Node.js age verification client — HMAC-signed API calls, webhook verification for Express, Hono, Next.js and other Node.js frameworks |
| WordPress | ProofAge/wordpress-plugin | Age gate plugin for WordPress — WooCommerce age verification, age-restricted pages, adult content gating |
| Laravel | this repo | Laravel age verification client — HMAC-signed API calls, webhook handling, middleware for age-restricted routes |
| Next.js | ProofAge/demo | Full-stack age verification demo with JS SDK, server routes, and webhook receiver |
The MIT License (MIT). Please see License File for more information.