Partner API Integration Guide

Integrate your external system with Pulse to automatically provision partner accounts for your users.

The Partner Registration API allows external systems (like CARES project management) to:

  • Create pre-authorized registration requests for users
  • Generate unique registration URLs with pre-filled data
  • Receive webhook notifications when users complete registration

Base URL

https://pulse.cares.center/api/v1/partner

Flow Diagram

┌─────────────────┐         ┌─────────────────┐         ┌─────────────────┐
│  Your System    │         │   Pulse API     │         │      User       │
└────────┬────────┘         └────────┬────────┘         └────────┬────────┘
         │                           │                           │
         │ 1. Create Request         │                           │
         │ ────────────────────────> │                           │
         │                           │                           │
         │ 2. Receive token          │                           │
         │ <──────────────────────── │                           │
         │                           │                           │
         │ 3. Confirm Request        │                           │
         │ ────────────────────────> │                           │
         │                           │                           │
         │ 4. Receive registration   │                           │
         │    URL                    │                           │
         │ <──────────────────────── │                           │
         │                           │                           │
         │ 5. Send URL to user ─────────────────────────────────>│
         │                           │                           │
         │                           │ 6. User completes form    │
         │                           │ <─────────────────────────│
         │                           │                           │
         │ 7. Webhook notification   │                           │
         │ <──────────────────────── │                           │

All API requests require HMAC-SHA256 signature authentication.

Required Headers

Header Description
X-Partner-Key Your API key (starts with pak_)
X-Partner-Signature HMAC-SHA256 signature
X-Partner-Timestamp Unix timestamp (seconds)
Content-Type application/json (for POST/DELETE)

Generating the Signature

The signature is calculated as:

signature = HMAC-SHA256(timestamp + "." + request_body, api_secret)
function generateSignature(string $body, string $secret, int $timestamp): string
{
    $signatureBase = $timestamp . '.' . $body;
    return hash_hmac('sha256', $signatureBase, $secret);
}

// Usage
$timestamp = time();
$body = json_encode(['organization_name' => 'ACME Corp', 'email' => 'john@acme.com']);
$signature = generateSignature($body, $apiSecret, $timestamp);
import hmac
import hashlib
import time
import json

def generate_signature(body: str, secret: str, timestamp: int) -> str:
    signature_base = f"{timestamp}.{body}"
    return hmac.new(
        secret.encode(),
        signature_base.encode(),
        hashlib.sha256
    ).hexdigest()

# Usage
timestamp = int(time.time())
body = json.dumps({'organization_name': 'ACME Corp', 'email': 'john@acme.com'})
signature = generate_signature(body, api_secret, timestamp)
const crypto = require('crypto');

function generateSignature(body, secret, timestamp) {
    const signatureBase = `${timestamp}.${body}`;
    return crypto.createHmac('sha256', secret)
        .update(signatureBase)
        .digest('hex');
}

// Usage
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ organization_name: 'ACME Corp', email: 'john@acme.com' });
const signature = generateSignature(body, apiSecret, timestamp);

Step 1: Create Registration Request

When a user in your system needs a Pulse partner account, create a registration request.

POST /api/v1/partner/request

Request Body:

{
    "organization_name": "ACME Corporation",
    "display_name": "John Doe",
    "email": "john@acme.com",
    "project_name": "CARES Initiative 2026",
    "callback_url": "https://your-system.com/webhooks/pulse",
    "callback_secret": "your_webhook_secret",
    "expires_in": 86400
}

Response:

{
    "success": true,
    "data": {
        "request_token": "prr_abc123...",
        "verify_url": "https://pulse.cares.center/api/v1/partner/request/prr_abc123.../status",
        "expires_at": "2026-01-19T18:00:00Z",
        "status": "pending"
    }
}

Step 2: Confirm Request & Get Registration URL

After creating the request, confirm it to get the registration URL. This is when you link your internal user ID.

POST /api/v1/partner/request/{request_token}/confirm

Request Body:

{
    "external_user_id": "your_internal_user_id_12345"
}

Response:

{
    "success": true,
    "data": {
        "registration_url": "https://pulse.cares.center/admin/register.php?token=prr_abc123...",
        "status": "confirmed"
    }
}

Step 3: Send Registration URL to User

Send the registration_url to your user via email, in-app notification, or direct redirect.

When the user visits this URL, they'll see a registration form with pre-filled organization name and email (read-only), and just need to set their password.

Step 4: Handle Webhook (Optional)

When the user completes registration, Pulse sends a webhook to your callback_url.

{
    "event": "partner.registration.completed",
    "request_token": "prr_abc123...",
    "external_user_id": "your_internal_user_id_12345",
    "tenant": {
        "id": 42,
        "uuid": "550e8400-e29b-...",
        "name": "ACME Corporation"
    },
    "user": {
        "id": 101,
        "email": "john@acme.com",
        "name": "John Doe"
    },
    "completed_at": "2026-01-18T19:30:00Z"
}
POST /api/v1/partner/request

Create a new partner registration request.

Field Required Description
organization_nameYesName for the new Pulse tenant
emailYesUser's email (must be unique)
display_nameNoUser's display name
project_nameNoAssociated project name
callback_urlNoWebhook URL for notifications
callback_secretNoSecret for webhook signature
expires_inNoSeconds until expiry (default: 86400)
GET /api/v1/partner/request/{token}/status

Returns the current status of a registration request.

POST /api/v1/partner/request/{token}/confirm

Confirms a pending request and returns the registration URL.

DELETE /api/v1/partner/request/{token}

Cancels a pending or confirmed request.

When a user completes registration, Pulse sends a POST request to your callback_url.

Webhook Headers

Header Description
X-Pulse-Event partner.registration.completed
X-Pulse-Signature HMAC-SHA256 signature (if callback_secret provided)
X-Pulse-Timestamp Unix timestamp

Verifying Webhook Signature

function verifyWebhookSignature(string $payload, string $signature, string $timestamp, string $secret): bool
{
    $expectedSignature = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
    return hash_equals($expectedSignature, $signature);
}

// Usage
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PULSE_SIGNATURE'];
$timestamp = $_SERVER['HTTP_X_PULSE_TIMESTAMP'];

if (!verifyWebhookSignature($payload, $signature, $timestamp, $callbackSecret)) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($payload, true);
// Process webhook...

Error Response Format

{
    "success": false,
    "error": {
        "code": "ERROR_CODE",
        "message": "Human-readable error message"
    }
}

Error Codes

Code HTTP Description
INVALID_API_KEY401API key not found or revoked
INVALID_SIGNATURE401Signature invalid or timestamp expired
RATE_LIMIT_EXCEEDED429Too many requests
VALIDATION_ERROR400Invalid request data
EMAIL_ALREADY_REGISTERED409Email already has a Pulse account
REQUEST_NOT_FOUND404Token not found
REQUEST_EXPIRED410Request has expired
<?php

class PulsePartnerAPI
{
    private string $apiKey;
    private string $apiSecret;
    private string $baseUrl = 'https://pulse.cares.center/api/v1/partner';

    public function __construct(string $apiKey, string $apiSecret)
    {
        $this->apiKey = $apiKey;
        $this->apiSecret = $apiSecret;
    }

    /**
     * Create registration and get URL in one call
     */
    public function createPartnerRegistration(
        string $organizationName,
        string $email,
        ?string $displayName = null,
        ?string $projectName = null,
        ?string $externalUserId = null,
        ?string $callbackUrl = null
    ): array {
        // Step 1: Create request
        $createData = [
            'organization_name' => $organizationName,
            'email' => $email,
        ];
        if ($displayName) $createData['display_name'] = $displayName;
        if ($projectName) $createData['project_name'] = $projectName;
        if ($callbackUrl) $createData['callback_url'] = $callbackUrl;

        $createResult = $this->request('POST', '/request', $createData);

        if (!$createResult['success']) {
            return $createResult;
        }

        $token = $createResult['data']['request_token'];

        // Step 2: Confirm and get URL
        $confirmData = $externalUserId ? ['external_user_id' => $externalUserId] : [];
        $confirmResult = $this->request('POST', "/request/{$token}/confirm", $confirmData);

        if (!$confirmResult['success']) {
            return $confirmResult;
        }

        return [
            'success' => true,
            'data' => [
                'request_token' => $token,
                'registration_url' => $confirmResult['data']['registration_url'],
                'expires_at' => $createResult['data']['expires_at'],
            ]
        ];
    }

    private function request(string $method, string $endpoint, array $data = []): array
    {
        $url = $this->baseUrl . $endpoint;
        $timestamp = time();
        $body = $method !== 'GET' ? json_encode($data) : '';
        $signature = hash_hmac('sha256', $timestamp . '.' . $body, $this->apiSecret);

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => [
                'X-Partner-Key: ' . $this->apiKey,
                'X-Partner-Signature: ' . $signature,
                'X-Partner-Timestamp: ' . $timestamp,
                'Content-Type: application/json',
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POSTFIELDS => $body ?: null,
        ]);

        $response = curl_exec($ch);
        curl_close($ch);
        return json_decode($response, true);
    }
}

// Usage
$pulse = new PulsePartnerAPI('pak_your_key', 'pas_your_secret');

$result = $pulse->createPartnerRegistration(
    organizationName: 'ACME Corporation',
    email: 'john@acme.com',
    displayName: 'John Doe',
    projectName: 'CARES Initiative 2026',
    externalUserId: 'user_12345'
);

if ($result['success']) {
    echo "Send this URL to user: " . $result['data']['registration_url'];
}
import hmac
import hashlib
import time
import json
import requests
from typing import Optional, Dict, Any

class PulsePartnerAPI:
    def __init__(self, api_key: str, api_secret: str):
        self.api_key = api_key
        self.api_secret = api_secret
        self.base_url = 'https://pulse.cares.center/api/v1/partner'

    def create_partner_registration(
        self,
        organization_name: str,
        email: str,
        display_name: Optional[str] = None,
        project_name: Optional[str] = None,
        external_user_id: Optional[str] = None,
        callback_url: Optional[str] = None
    ) -> Dict[str, Any]:
        # Step 1: Create request
        create_data = {'organization_name': organization_name, 'email': email}
        if display_name: create_data['display_name'] = display_name
        if project_name: create_data['project_name'] = project_name
        if callback_url: create_data['callback_url'] = callback_url

        create_result = self._request('POST', '/request', create_data)
        if not create_result.get('success'):
            return create_result

        token = create_result['data']['request_token']

        # Step 2: Confirm
        confirm_data = {'external_user_id': external_user_id} if external_user_id else {}
        confirm_result = self._request('POST', f'/request/{token}/confirm', confirm_data)

        if not confirm_result.get('success'):
            return confirm_result

        return {
            'success': True,
            'data': {
                'request_token': token,
                'registration_url': confirm_result['data']['registration_url'],
                'expires_at': create_result['data']['expires_at'],
            }
        }

    def _request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict:
        url = self.base_url + endpoint
        timestamp = int(time.time())
        body = json.dumps(data) if data and method != 'GET' else ''
        signature = hmac.new(
            self.api_secret.encode(),
            f"{timestamp}.{body}".encode(),
            hashlib.sha256
        ).hexdigest()

        response = requests.request(
            method=method, url=url,
            headers={
                'X-Partner-Key': self.api_key,
                'X-Partner-Signature': signature,
                'X-Partner-Timestamp': str(timestamp),
                'Content-Type': 'application/json',
            },
            data=body if body else None
        )
        return response.json()

# Usage
pulse = PulsePartnerAPI('pak_your_key', 'pas_your_secret')
result = pulse.create_partner_registration(
    organization_name='ACME Corporation',
    email='john@acme.com',
    display_name='John Doe',
    external_user_id='user_12345'
)

if result['success']:
    print(f"Send this URL to user: {result['data']['registration_url']}")
# Set your credentials
API_KEY="pak_your_key"
API_SECRET="pas_your_secret"

# Generate signature
TIMESTAMP=$(date +%s)
BODY='{"organization_name":"Test Corp","email":"test@example.com"}'
SIGNATURE=$(echo -n "${TIMESTAMP}.${BODY}" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $2}')

# Create request
curl -X POST "https://pulse.cares.center/api/v1/partner/request" \
  -H "Content-Type: application/json" \
  -H "X-Partner-Key: $API_KEY" \
  -H "X-Partner-Signature: $SIGNATURE" \
  -H "X-Partner-Timestamp: $TIMESTAMP" \
  -d "$BODY"

Best Practices

  • Store tokens: Save the request_token in your database linked to your user
  • Handle expiration: Requests expire after 24 hours; create new ones if needed
  • Verify webhooks: Always verify webhook signatures in production
  • Idempotency: Check status before creating duplicate requests for the same email
  • Retry logic: Implement exponential backoff for rate limiting