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);
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"
}
/api/v1/partner/request
Create a new partner registration request.
| Field | Required | Description |
|---|---|---|
organization_name | Yes | Name for the new Pulse tenant |
email | Yes | User's email (must be unique) |
display_name | No | User's display name |
project_name | No | Associated project name |
callback_url | No | Webhook URL for notifications |
callback_secret | No | Secret for webhook signature |
expires_in | No | Seconds until expiry (default: 86400) |
/api/v1/partner/request/{token}/status
Returns the current status of a registration request.
/api/v1/partner/request/{token}/confirm
Confirms a pending request and returns the registration URL.
/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_KEY | 401 | API key not found or revoked |
INVALID_SIGNATURE | 401 | Signature invalid or timestamp expired |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
VALIDATION_ERROR | 400 | Invalid request data |
EMAIL_ALREADY_REGISTERED | 409 | Email already has a Pulse account |
REQUEST_NOT_FOUND | 404 | Token not found |
REQUEST_EXPIRED | 410 | Request 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'];
}
# 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