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 Sumsub Sdk Laravel Package

shureban/laravel-sumsub-sdk

Laravel SDK that implements Sumsub API routes for identity verification workflows. Install via Composer, register the service provider, and publish the config to swap interfaces for concrete implementations.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require shureban/laravel-sumsub-sdk
php artisan vendor:publish --provider="Shureban\LaravelSumsubSdk\SumsubServiceProvider" --force
php artisan vendor:publish --provider="Shureban\LaravelObjectMapper\ObjectMapperServiceProvider" --force

The README tells you to register Shureban\LaravelSumsubSdk\SumsubServiceProvider::class in config/app.php. On Laravel 5.5+ package auto-discovery usually handles this — check bootstrap/cache/packages.php before adding it manually, and don't register it twice.

Where to look first, in order

  1. config/sumsub.php (after publishing). This is the only place credentials and bindings live. Expect: app token, secret key, base URL / sandbox toggle, and the interface→class bindings the package advertises. Set them via env() in .env, never hard-code.
  2. src/ for the DTO/model layer — Info, CompanyInfo, ReviewResult, Questionnaire, plus the document-type, review-status and reject-label enums. These classes are the documentation; the README's ## Usage section is empty, so the DTOs and the client class are your API reference.
  3. Sumsub's own API docs for endpoint semantics — this package is a thin wrapper, so field meanings, level names, and lifecycle rules come from Sumsub, not from here.

First use case to build end-to-end

Applicant creation + WebSDK token issuance + webhook receipt. That single vertical slice exercises config, HTTP auth, response mapping, and signature verification, and it's the one every KYC integration needs on day one:

  • On user signup, create an applicant at your configured levelName and persist the returned applicantId on your users table (indexed, unique).
  • When the user opens the verification page, request a short-lived SDK access token server-side and hand it to the frontend. Never cache or persist that token.
  • Expose a webhook endpoint, verify the payload digest, store the event, and flip your own kyc_status column.

Implementation Patterns

Treat the SDK as a repository layer, not a facade sprinkled through controllers

Wrap it in your own KycService / VerificationGateway interface bound in a service provider. The package's config already lets you swap interface→class implementations, but a domain-level wrapper is what lets you upgrade (or drop) the SDK without touching controllers. The underlying call is plain REST against Sumsub, so a fallback implementation is cheap.

Applicant lifecycle as a state machine

Model it explicitly: not_started → applicant_created → pending → (approved | rejected | resubmitted). Drive transitions only from webhook events plus a periodic reconciliation poll (GET applicant status) for events you missed. Sumsub is at-least-once on webhooks, and your own create call can race the first event — do not assume ordering.

Webhook controller: verify, persist, dispatch, 200

public function __invoke(Request $request)
{
    $raw    = $request->getContent();               // NOT $request->all()
    $digest = $request->header('X-Payload-Digest');

    $expected = hash_hmac('sha256', $raw, config('sumsub.secret_key'));

    abort_unless(is_string($digest) && hash_equals($expected, $digest), 401);

    KycWebhook::create([
        'type'       => $request->json('type'),
        'applicant_id' => $request->json('applicantId'),
        'payload'    => $raw,
        'received_at' => now(),
    ]);

    ProcessKycWebhook::dispatch($raw)->onQueue('kyc');

    return response()->noContent();
}

Verify with hash_equals, not ===. Keep the raw body — store it verbatim so you can replay/reduce an incident later without asking Sumsub for a resend.

Queue all outbound API calls

Applicant creation, document fetches, and status checks belong in jobs with a timeout and retry policy, not in an HTTP request a user is waiting on. Sumsub calls are network-bound and rate-limited; a slow one shouldn't take down your signup path.

Idempotency on your side

Add a unique index on (applicant_id, type, correlation_id) where available, or on a hash of the raw payload. Duplicate applicantReviewed events are normal, and the second one should be a no-op, not a second email or a second ledger entry.

Multi-jurisdiction / multi-tenant

Recent releases added per-jurisdiction API keys and a separate secret key. If you operate in several regions, don't put one client in the container as a singleton — resolve the client per tenant/jurisdiction at call time from a small factory, and key your idempotency records by the same tenant key.

Testing

Use Http::fake() with payloads captured from Sumsub's sandbox and assert against the package's DTOs. The published DTO classes double as excellent webhook fixtures — constructing a ReviewResult in a test is faster and more accurate than hand-writing JSON. Swap the gateway binding in TestCase so feature tests never hit the network.

Gotchas and Tips

  • The README has no usage section. That is the single biggest day-one cost. Budget an hour to read src/ and the config file before estimating any work. Do not infer method names from Sumsub's REST paths; they won't match.
  • Two providers, not one. laravel-object-mapper does the JSON→object hydration. Publishing only the Sumsub config leaves you with mapping that fails on nested fixedInfo, companyInfo, or questionnaire structures. Publish both, and re-publish after upgrading.
  • config:cache is a trap. Credentials and bindings are read from config at resolution time. Change .env → rerun php artisan config:cache, or you'll debug a "wrong token" for twenty minutes.
  • Signature mismatches are almost always body mutation. Use $request->getContent(). Re-encoding $request->all() with json_encode reorders keys and strips whitespace, and the HMAC will never match. Also confirm no middleware is rewriting the request body, and that your webhook route is exempt from CSRF (api middleware group, not web).
  • App token vs. secret key are different secrets for different directions. Token authenticates your outbound calls; the secret key validates inbound webhooks. Mixing them up produces a 401 on one side and silent webhook acceptance failures on the other — and the latter is worse, because you'll process forged events.
  • Enums are added release by release, and occasionally removed. There is a v0.0.33 "remove reject labels" release in the history. A pinned enum class can throw when Sumsub sends a reject label or document subtype you don't have a case for. Never let an unknown label crash the webhook worker — catch the mapping exception, store the raw string, and alert.
  • Sandbox ≠ production level names. Applicant creation fails confusingly if levelName doesn't exist in the target environment. Keep the level name in config with separate values per environment.
  • applicant reset is destructive. It wipes submitted data and is not reversible. If you expose it, put it behind an explicit admin action with an audit record, never on a retry path.
  • Pin the major version. The changelog shows rapid 0.0.x → 1.0 → 2.x churn with breaking changes, plus a jump straight to Laravel 13 support. Pin ^2.3 and upgrade deliberately with the full compare diff read, not just the release headline.
  • Vendor risk is real: one maintainer, ~1 star, 0 dependents. Don't let it leak into more than one or two classes. Keep the raw webhook payload and applicant IDs in your own tables so migrating to direct HTTP calls is a swap of one adapter, not a rewrite.
  • Where to extend: the publishable config's interface bindings are the intended customization point. Anything else — retry policy, logging, base URL per tenant — wrap in your own decorator rather than editing vendor files, so composer update stays safe.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
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