Quick Start

Get Blackwalnut running and issue your first license in just a few steps.

1

Deploy with Docker

The fastest way to get started is with Docker. First, run migrations, then start the server:

terminal
# Run database migrations first
$ docker run --rm \
  -e DATABASE_URL="ecto://user:pass@host/db" \
  -e SECRET_KEY_BASE="your-secret-key" \
  -e ENCRYPTION_KEY="your-encryption-key" \
  goodway/blackwalnut /app/bin/migrate

# Start Blackwalnut
$ docker run -d \
  --name blackwalnut \
  -p 4000:4000 \
  -e DATABASE_URL="ecto://user:pass@host/db" \
  -e SECRET_KEY_BASE="your-secret-key" \
  -e ENCRYPTION_KEY="your-encryption-key" \
  -e PHX_HOST="localhost" \
  goodway/blackwalnut

Note: Generate secrets with: openssl rand -hex 64 for SECRET_KEY_BASE and openssl rand -base64 32 for ENCRYPTION_KEY.

Environment Variables

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string
SECRET_KEY_BASE Yes 64+ char secret for signing cookies
ENCRYPTION_KEY Yes Base64-encoded key for encrypting sensitive data
PHX_HOST No Production hostname (default: example.com)
PORT No HTTP port (default: 4000)
POOL_SIZE No Database connection pool size (default: 10)
LICENSE_ISSUER No Name shown as license issuer
CORS_ORIGINS No Comma-separated allowed origins for CORS
DNS_CLUSTER_QUERY No DNS query for multi-node clustering
2

Create Your Account

Open http://localhost:4000 and register your account. Then create an API key from the settings page.

3

Create an Application

Create an application via the API. This automatically generates a 4096-bit RSA key pair for signing licenses.

create-app.sh
$ curl -X POST http://localhost:4000/api/v1/apps \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My App",
    "slug": "my-app"
  }'

# Response
{
  "data": {
    "id": "app_abc123",
    "name": "My App",
    "slug": "my-app",
    "public_key_pem": "-----BEGIN PUBLIC KEY-----\n..."
  }
}
4

Set Up Tiers

Create pricing tiers for your application. Each license will be associated with a tier.

create-tier.sh
$ curl -X POST http://localhost:4000/api/v1/apps/my-app/tiers \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro",
    "slug": "pro",
    "position": 1
  }'
5

Issue a License

Create a customer and issue them a license. The response includes a signed JWT token.

issue-license.sh
# Create a customer first
$ curl -X POST http://localhost:4000/api/v1/apps/my-app/customers \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "name": "John Doe"}'

# Issue a license
$ curl -X POST http://localhost:4000/api/v1/apps/my-app/licenses \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "cus_abc123",
    "tier_id": "tier_xyz789",
    "installation_id": "550e8400-e29b-41d4-a716-446655440000",
    "duration_days": 365
  }'

# Response includes the signed JWT token
{
  "data": {
    "id": "lic_def456",
    "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tier": "pro",
    "status": "active",
    "expires_at": "2027-01-23T00:00:00Z"
  }
}
6

Verify the License

Verify licenses using the public API endpoint, or decode the JWT offline using the public key.

verify.py
import jwt

# Your app's public key (fetch once and cache)
public_key = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
-----END PUBLIC KEY-----"""

def verify_license(token):
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            audience="my-app"
        )
        return {
            "valid": True,
            "tier": payload["tier"],
            "features": payload.get("features", [])
        }
    except jwt.InvalidTokenError as e:
        return {"valid": False, "error": str(e)}