Getting Started

Go from zero to protected in under five minutes

Step 1: Create your account

Sign up at obfuscura.com/register. Every account starts with a 14-day free trial with full access. No credit card required.

Step 2: Create a project

From your dashboard, click New Project. Give it a name (e.g., "My WordPress Plugin"). Each project gets its own API key and can have multiple license types.

Step 3: Encode your files

Navigate to the Encode tab. Upload a single PHP file or ZIP archive. Select encoding options and click Encode. Protected files are ready in seconds.

Step 4: Include the loader

Download the runtime loader from your project settings. Place obfuscura-loader.php in your project root and add:

require_once __DIR__ . '/obfuscura-loader.php';

Your encoded files now execute normally. The loader handles license validation transparently.

Step 5: Issue a license

Go to Licenses > Create License. Choose a license type, set restrictions, and generate the key. Send it to your customer with the encoded files.

Encoding Your Code

How to prepare and encode your PHP files for distribution

What gets encoded

Obfuscura encodes .php files only. Non-PHP files (images, CSS, JavaScript, configs) pass through unchanged. Upload your entire project as a ZIP and only PHP files are transformed.

Encoding options

String Encryption: Encrypts all string literals. Enabled by default.

Control Flow Flattening: Restructures logic flow to resist decompilation. Enabled by default. May increase file size 10-20%.

Dead Code Injection: Inserts realistic non-functional code paths. Optional. Increases analysis time significantly.

License Binding: Requires valid license at runtime. Disable for open-core models where some files are free.

Batch encoding via API

curl -X POST https://obfuscura.com/api/v1/encode \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@my-plugin.zip" \
  -F "options[string_encryption]=true" \
  -F "options[control_flow]=true" \
  -o my-plugin-encoded.zip

The Runtime Loader

A single PHP file that powers everything

How it works

The loader registers a custom stream wrapper and autoloader. When an encoded file is included, the loader intercepts the request, decodes the file in memory, and passes it to PHP's execution engine. The decoded source never touches disk.

The loader also handles license validation on a configurable schedule, caching results locally.

Configuration

The loader reads from environment variables or obfuscura.json:

{
  "license_key": "OBF-XXXX-XXXX-XXXX-XXXX",
  "project_api_key": "proj_xxxxxxxxxxxxx",
  "validation_interval": "daily",
  "offline_grace_hours": 72,
  "log_level": "error"
}

Hosting compatibility

Works on any environment running PHP 8.0+. No PECL extensions, no php.ini changes. Tested on:

  • Shared hosting (cPanel, Plesk, DreamHost)
  • VPS (DigitalOcean, Linode, Vultr)
  • Cloud (AWS EC2, GCP, Azure)
  • Managed platforms (Laravel Forge, Ploi, ServerPilot)
  • Containers (Docker, Kubernetes)

License Management

Creating and managing licenses for your customers

License types

Define license types as templates with rules:

  • Duration: Perpetual, fixed-date, or relative (365 days from activation)
  • Activation limit: How many domains/servers per key
  • Domain locking: Bind to specific domains
  • Features: Feature-gated licensing
  • Validation interval: Per-request, hourly, or daily
  • Offline grace: How long files work without API access

Issuing licenses via API

curl -X POST https://obfuscura.com/api/v1/licenses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "license_type_id": 1,
    "customer_email": "client@example.com",
    "customer_name": "Acme Corp",
    "domains": ["acme.com", "staging.acme.com"],
    "expires_at": "2027-01-01"
  }'

Returns a key like OBF-A1B2-C3D4-E5F6-G7H8. Include this key in the obfuscura.json that ships with your encoded project.

Revoking licenses

Revoke instantly from the dashboard. Encoded files stop after the offline grace period expires. Modify domains, expiration, or activation limits at any time.

CI/CD Integration

Encode protected builds automatically on every release

Overview

Obfuscura's REST API lets you encode files as part of your deployment pipeline. Add a single step to your workflow and ship protected artifacts without manual intervention.

GitHub Actions

# .github/workflows/release.yml
- name: Encode PHP files
  run: |
    curl -X POST https://obfuscura.com/api/v1/encode \
      -H "Authorization: Bearer ${{ secrets.OBFUSCURA_KEY }}" \
      -F "file=@dist/my-plugin.zip" \
      -F "options[string_encryption]=true" \
      -F "options[control_flow]=true" \
      -o dist/my-plugin-encoded.zip

GitLab CI

# .gitlab-ci.yml
encode:
  stage: build
  script:
    - curl -X POST https://obfuscura.com/api/v1/encode
        -H "Authorization: Bearer $OBFUSCURA_KEY"
        -F "file=@dist/my-plugin.zip"
        -o dist/my-plugin-encoded.zip
  artifacts:
    paths:
      - dist/my-plugin-encoded.zip

Any REST-capable runner

Jenkins, Bitbucket Pipelines, CircleCI, or any system that can execute a curl command works. Store your API key as a secret and call the encode endpoint. Results are streamed back as a download — no temporary storage on our side.

Unlimited encoding

All plans include unlimited encoding runs via the API. No per-encode fees and no per-server charges.

API Reference

RESTful endpoints for every operation

Obfuscura API

v1.0
https://obfuscura.com/api/v1
🔒 Authorization — All requests require a Bearer token.
Include your project API key in the header: Authorization: Bearer proj_xxxxxxxxxxxxx
Get your API key from Dashboard → Project Settings → API Keys.
🔑 Licenses
POST /license/validate Validate a license key
Request Body — application/json
NameTypeDescription
license_key *requiredstringThe license key to validate (e.g. OBF-XXXX-XXXX-XXXX-XXXX)
project_api_key *requiredstringYour project API key
domain *requiredstringDomain requesting validation
ipstringServer IP address (auto-detected if omitted)
Example Request
{ "license_key": "OBF-A1B2-C3D4-E5F6-G7H8", "project_api_key": "proj_xxxxxxxxxxxxx", "domain": "example.com" }
Responses
200 License is valid
{ "valid": true, "license": { "key": "OBF-A1B2-C3D4-E5F6-G7H8", "status": "active", "expires_at": "2027-01-01T00:00:00Z", "features": ["pro", "api_access"], "activations_used": 1, "activations_limit": 5 } }
401 Invalid or missing API key
403 License expired, revoked, or domain mismatch
422 Validation error (missing required fields)
POST /license/activate Activate a license on a domain
Request Body — application/json
NameTypeDescription
license_key *requiredstringThe license key to activate
project_api_key *requiredstringYour project API key
domain *requiredstringDomain to bind this activation to
device_namestringHuman-readable label (e.g. "Production Server")
ipstringServer IP for IP-locked licenses
hostnamestringServer hostname for hostname-locked licenses
Example Request
{ "license_key": "OBF-A1B2-C3D4-E5F6-G7H8", "project_api_key": "proj_xxxxxxxxxxxxx", "domain": "example.com", "device_name": "Production Server" }
Responses
200 License activated successfully
{ "activated": true, "activation": { "domain": "example.com", "device_name": "Production Server", "activated_at": "2026-02-09T22:30:00Z", "activations_remaining": 4 } }
403 Activation limit reached
409 Domain already activated
DELETE /license/deactivate Remove a domain activation
Request Body — application/json
NameTypeDescription
license_key *requiredstringThe license key
project_api_key *requiredstringYour project API key
domain *requiredstringDomain to deactivate
Example Request
{ "license_key": "OBF-A1B2-C3D4-E5F6-G7H8", "project_api_key": "proj_xxxxxxxxxxxxx", "domain": "example.com" }
Responses
200 Activation removed
{ "deactivated": true, "domain": "example.com", "activations_remaining": 5 }
404 Activation not found for this domain
GET /licenses List all licenses for a project
Query Parameters
NameTypeDescription
statusstringFilter by status: active, expired, revoked, suspended
customer_emailstringFilter by customer email
pageintegerPage number (default: 1)
per_pageintegerResults per page (default: 25, max: 100)
Responses
200 Paginated list of licenses
{ "data": [ { "key": "OBF-A1B2-C3D4-E5F6-G7H8", "status": "active", "customer_email": "client@@example.com", "customer_name": "Acme Corp", "activations_used": 1, "activations_limit": 5, "expires_at": "2027-01-01T00:00:00Z", "created_at": "2026-02-09T12:00:00Z" } ], "meta": { "current_page": 1, "total": 47, "per_page": 25 } }
POST /licenses Create a new license
Request Body — application/json
NameTypeDescription
license_type_id *requiredintegerID of the license type template
customer_email *requiredstringCustomer email address
customer_namestringCustomer or company name
domainsarrayPre-authorized domains
expires_atstringISO 8601 expiration date (null for perpetual)
featuresarrayFeature flags to enable
metadataobjectCustom key-value pairs
Example Request
{ "license_type_id": 1, "customer_email": "client@@example.com", "customer_name": "Acme Corp", "domains": ["acme.com", "staging.acme.com"], "expires_at": "2027-01-01", "features": ["pro", "api_access"], "metadata": { "order_id": "INV-1234" } }
Responses
201 License created
{ "license": { "key": "OBF-X9Y8-W7V6-U5T4-S3R2", "status": "active", "customer_email": "client@@example.com", "expires_at": "2027-01-01T00:00:00Z", "activations_limit": 5, "features": ["pro", "api_access"] } }
422 Validation error
PUT /licenses/{license_key} Update a license
Path Parameters
NameTypeDescription
license_key *requiredstringThe license key to update
Request Body — application/json
NameTypeDescription
statusstringactive, suspended, or revoked
expires_atstringNew expiration date (ISO 8601)
featuresarrayReplace feature flags
activations_limitintegerMax concurrent activations
metadataobjectMerge with existing metadata
Responses
200 License updated
404 License not found
DELETE /licenses/{license_key} Revoke a license permanently
Path Parameters
NameTypeDescription
license_key *requiredstringThe license key to revoke
Responses
200 License revoked, all activations removed
{ "revoked": true, "license_key": "OBF-A1B2-C3D4-E5F6-G7H8", "activations_removed": 3 }
404 License not found
⚙️ Encoding
POST /encode Encode PHP files
Request Body — multipart/form-data
NameTypeDescription
file *requiredfilePHP file or ZIP archive to encode
options[string_encryption]booleanEncrypt string literals (default: true)
options[control_flow]booleanFlatten control flow (default: true)
options[dead_code]booleanInject dead code paths (default: false)
options[license_binding]booleanRequire license at runtime (default: true)
Example Request (cURL)
curl -X POST https://obfuscura.com/api/v1/encode \ -H "Authorization: Bearer proj_xxxxxxxxxxxxx" \ -F "file=@my-plugin.zip" \ -F "options[string_encryption]=true" \ -F "options[control_flow]=true" \ -F "options[dead_code]=false" \ -o my-plugin-encoded.zip
Responses
200 Encoded file (binary download)
422 Invalid PHP syntax in uploaded file
413 File exceeds plan size limit
GET /encode/{job_id}/status Check encoding job status
Path Parameters
NameTypeDescription
job_id *requiredstringEncoding job ID (returned in X-Job-Id header for async jobs)
Responses
200 Job status
{ "job_id": "enc_8f3k2j1m", "status": "completed", "files_processed": 142, "files_skipped": 38, "download_url": "/api/v1/encode/enc_8f3k2j1m/download", "expires_at": "2026-02-10T00:30:00Z" }
🔔 Webhooks
GET /webhooks List configured webhook endpoints
Responses
200 Array of webhook configurations
{ "data": [ { "id": 1, "url": "https://example.com/webhooks/obfuscura", "events": ["license.activated", "license.expired"], "active": true } ] }
POST /webhooks Register a webhook endpoint
Request Body — application/json
NameTypeDescription
url *requiredstringHTTPS endpoint URL
events *requiredarrayEvents to subscribe to (see list below)
secretstringSigning secret for payload verification
Available Events
"license.activated" // License activated on a domain "license.deactivated" // Activation removed "license.expired" // License reached expiration date "license.revoked" // License manually revoked "license.suspended" // Suspended (e.g. payment failure) "validation.failed" // Validation rejected (domain/IP mismatch) "tamper.detected" // Loader or encoded file modified "encoding.completed" // Async encoding job finished
Responses
201 Webhook registered
422 Invalid URL or events

Stripe Integration

Automate license provisioning with Stripe subscriptions

Connect your Stripe account, map products to license types, and Obfuscura will automatically:

  • Create a license when a new subscription starts
  • Suspend a license when a payment fails
  • Reactivate a license when payment succeeds
  • Revoke a license when a subscription is canceled

Your entire sales workflow — from checkout to code protection — is fully automated.

Troubleshooting

Common issues and solutions

"License validation failed"

Check license_key and project_api_key in obfuscura.json. Verify the license is active and domain matches. Check firewall rules for outbound HTTPS on port 443.

"Loader integrity check failed"

The loader file was modified. Re-download a fresh copy from your project settings. The loader includes a self-verification checksum.

Encoded files not executing

Ensure the loader is required before any encoded files. It must be the first require in your entry point. Verify PHP 8.0+: php -v.

Stop Losing Revenue to Unprotected PHP

14-day free trial. Full feature access. No credit card required. No server extensions to install.