Overview

The Resoon API provides Iranian recipient onboarding and KYC verification services for crypto-to-fiat payouts. PayGear integrates with Resoon to manage user profiles, bank accounts, and verification status.

Base URL: https://resoon.paygear.io/api/v1

Architecture: PayGear calls Resoon's REST API to create/query users. Resoon sends webhook events to PayGear when user status changes occur.

Authentication (HMAC-SHA256)

All v1 API endpoints require HMAC-SHA256 signature authentication to ensure request authenticity and prevent tampering.

Required for: All /api/v1/* endpoints and /api/transaction-request

Required Headers

  • X-API-Key: Your API key (format: pk_<32-hex-chars>)
  • X-Timestamp: Unix timestamp in milliseconds (must be within 5 minutes)
  • X-Signature: HMAC-SHA256 signature of the request
  • Content-Type: application/json

Signature Generation

The signature is created by concatenating METHOD + PATH + TIMESTAMP + BODY, then hashing with your API secret:

import crypto from 'crypto';

function generateHMACSignature(
  secret: string,
  method: string,
  path: string,
  timestamp: string,
  body: string = ''
): string {
  const message = `${method.toUpperCase()}${path}${timestamp}${body}`;
  return crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');
}

// Example: POST request
const secret = 'your-64-char-hex-secret';
const timestamp = Date.now().toString();
const method = 'POST';
const path = '/api/v1/users';
const body = JSON.stringify({ person: {...} });

const signature = generateHMACSignature(
  secret, method, path, timestamp, body
);

// Add to request headers
headers: {
  'X-API-Key': 'pk_c9b5695b8d593f4107d37c04ae3d5d06',
  'X-Timestamp': timestamp,
  'X-Signature': signature,
  'Content-Type': 'application/json'
}

Python Example

import hmac
import hashlib
import time

def generate_hmac_signature(
    secret: str,
    method: str,
    path: str,
    timestamp: str,
    body: str = ""
) -> str:
    message = f"{method.upper()}{path}{timestamp}{body}"
    return hmac.new(
        secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

# Example: GET request
timestamp = str(int(time.time() * 1000))
path = "/api/v1/users/RSN-123/status"
signature = generate_hmac_signature(
    "your-secret", "GET", path, timestamp
)

Getting API Keys

  1. Navigate to page
  2. Click "Generate New Key"
  3. Add an optional description
  4. Copy the secret immediately - it's only shown once!
  5. Store both API key (pk_*) and secret securely

Security: Timestamps must be within 5 minutes of current time. Signatures prevent request tampering and replay attacks.

Common Authentication Errors

StatusErrorSolution
401Missing authentication headersInclude X-API-Key, X-Timestamp, X-Signature
401Invalid API key formatKey must start with pk_ and be 35 chars
401Timestamp too oldUse current timestamp (within 5 minutes)
401Invalid signatureVerify method, path, timestamp, and body are correct
POST

Create User

POST https://resoon.paygear.io/api/v1/users

Create a new recipient in Resoon's system for KYC verification. Validates Iranian national ID, mobile, IBAN, and bank card details.

Authentication

HMAC-SHA256 Required

See Authentication section above for signature generation details.

Request Body

{
  "person": {
    "name_en": {
      "first_name": "Ali",
      "middle_name": "Mohammad",
      "last_name": "Mohammadi"
    },
    "name_fa": {
      "first_name": "علی",
      "last_name": "محمدی"
    },
    "date_of_birth": "1991-08-06",
    "date_of_birth_shamsi": "1370/05/15",
    "codemelli": "0123456789",
    "contacts": {
      "mobile": "+989121234567",
      "email": "ali@example.com"
    }
  },
  "bank_accounts": [
    {
      "nickname": "حساب اصلی",
      "bank_name": "ملی",
      "iban": "IR820540102680020817909002",
      "card_number": "6037997123456789",
      "preferred": true
    }
  ]
}

Validation Rules

  • codemelli: Must be 10 digits with valid mod-11 checksum
  • mobile: Iranian format (+98912...) or local (0912...)
  • iban: Must be valid Iranian IBAN (IR + 24 digits, mod-97 checksum)
  • card_number: Must be 16 digits
  • bank_accounts: At least 1 required

Authentication: HMAC-SHA256 required - See Authentication section above for implementation details

Success Response (201 Created - New User)

{
  "resoonId": "RSN-1733512345678-a1b2c3d4",
  "status": "pending_verification",
  "message": "Profile received. Verification in progress."
}

Success Response (200 OK - Existing User with Discrepancies)

{
  "resoonId": "RSN-1733512345678-a1b2c3d4",
  "status": "pending_verification",
  "message": "Existing user found. New information added for verification.",
  "is_existing_user": true,
  "discrepancies": {
    "mobile_mismatch": {
      "existing": "+989121234567",
      "provided": "+989129999999",
      "new_mobile_added": true,
      "verification_required": true
    },
    "bank_accounts_added": [
      {
        "iban": "IR062960000000100324200002",
        "bank_name": "Bank Pasargad",
        "verification_required": true
      }
    ]
  }
}

Duplicate User Handling

When a user with the same codemelli (National ID) already exists:

  • Returns existing resoonId with status 200 (not 409 duplicate error)
  • Mobile mismatch: If mobile differs from existing, adds to pending_mobiles array for verification
  • New bank accounts: Adds any new IBANs with verification_status: "pending"
  • Returns detailed discrepancies object showing what was added
  • Sets is_existing_user: true flag

Error Response (422 Validation Error)

{
  "error": {
    "code": "invalid_format",
    "message": "Invalid Iranian IBAN format",
    "fields": ["bank_accounts[0].iban"]
  }
}

Example cURL Request

# First, generate signature (see Authentication section)
TIMESTAMP=$(date +%s000)
BODY='{"person":{"name_en":{"first_name":"Ali","last_name":"Mohammadi"},"date_of_birth":"1990-01-01","date_of_birth_shamsi":"1368/10/11",...}}'
SIGNATURE=$(echo -n "POST/api/v1/users${TIMESTAMP}${BODY}" |   openssl dgst -sha256 -hmac "your-secret" | cut -d' ' -f2)

curl -X POST https://resoon.paygear.io/api/v1/users \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_c9b5695b8d593f4107d37c04ae3d5d06" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Signature: ${SIGNATURE}" \
  -d '{"person":{"name_en":{"first_name":"Ali","last_name":"Mohammadi"},"date_of_birth":"1990-01-01","date_of_birth_shamsi":"1368/10/11",...}}'
GET

Get User Summary

GET https://resoon.paygear.io/api/v1/users/{resoonId}/summary

Retrieve basic user information with privacy masking. Mobile numbers and IBANs are partially masked to protect user privacy.

Authentication: HMAC-SHA256 required (see Authentication section)

Success Response (200 OK)

{
  "resoonId": "RSN-1733512345678-a1b2c3d4",
  "full_name": "علی محمدی",
  "mobile_masked": "0912***4567",
  "status": "verified",
  "bank_accounts_preview": [
    {
      "accountId": "acc-123",
      "nickname": "حساب اصلی",
      "bank_name": "ملی",
      "iban_last4": "9002",
      "preferred": true
    }
  ],
  "created_at": "2024-12-06T10:30:00Z"
}

Privacy Notes

  • Mobile: Shows first 4 and last 4 digits (0912***4567)
  • IBAN: Only last 4 digits shown
  • Card numbers: Never exposed via API
  • Full codemelli: Never exposed via API
GET

Get User Status

GET https://resoon.paygear.io/api/v1/users/{resoonId}/status

Check current verification lifecycle status.

Authentication: HMAC-SHA256 required (see Authentication section)

Possible Statuses

  • draft - Initial creation, not submitted
  • screening - Under automated screening
  • pending_verification - Awaiting manual review
  • verified - KYC approved, ready for payouts
  • rejected - KYC failed

Success Response (200 OK)

{
  "resoonId": "RSN-1733512345678-a1b2c3d4",
  "status": "verified",
  "status_reasons": []
}
POST

Register Webhook

POST https://resoon.paygear.io/api/v1/webhook-registration

Register PayGear's webhook URL to receive event notifications when user status changes, bank accounts are added, etc.

Authentication: HMAC-SHA256 required (see Authentication section)

Request Body

{
  "webhook_url": "https://api.paygear.com/webhooks/resoon",
  "webhook_secret": "your_webhook_secret_minimum_32_chars_long",
  "events": [
    "user.status.changed",
    "user.bank_account.added",
    "user.bank_account.preferred.changed"
  ]
}

Validation

  • webhook_url must use HTTPS protocol
  • webhook_secret should be strong (recommended 32+ characters)
  • events array is optional (defaults to all events)

Success Response (200 OK)

{
  "message": "Webhook registered successfully",
  "events": [
    "user.status.changed",
    "user.bank_account.added",
    "user.bank_account.preferred.changed"
  ],
  "registered_at": "2024-12-06T10:30:00Z"
}

Webhooks (Resoon → PayGear)

Resoon sends HMAC-signed webhook events to PayGear when lifecycle changes occur. PayGear must implement a webhook receiver endpoint and verify signatures.

Webhook Structure

POST https://api.paygear.com/webhooks/resoon
Content-Type: application/json
X-Resoon-Signature: sha256=<hmac_hex_digest>

{
  "event": {
    "event_id": "evt-status-1733512345678-a1b2c3d4",
    "event_type": "user.status.changed",
    "resoonId": "RSN-1733512345678-a1b2c3d4",
    "old_status": "pending_verification",
    "new_status": "verified",
    "occurred_at": "2024-12-06T10:30:00Z"
  }
}

Signature Verification (Node.js)

CRITICAL: PayGear MUST verify the X-Resoon-Signature header to ensure webhook authenticity and prevent spoofing attacks.

import { createHmac } from 'crypto';

function verifyResoonWebhook(
  body: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = 'sha256=' + 
    createHmac('sha256', secret)
      .update(body)
      .digest('hex');
  
  return signature === expectedSignature;
}

// Usage in Express.js webhook handler
app.post('/webhooks/resoon', 
  express.raw({ type: 'application/json' }), 
  (req, res) => {
    const signature = req.headers['x-resoon-signature'];
    const body = req.body.toString('utf8');
    
    if (!verifyResoonWebhook(body, signature, process.env.RESOON_WEBHOOK_SECRET)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    
    const payload = JSON.parse(body);
    handleResoonEvent(payload.event);
    
    res.json({ received: true });
});

Event Types

  • user.status.changed - User verification status changed (pending → verified, etc.)
  • user.bank_account.added - User added a new payout bank account
  • user.bank_account.preferred.changed - User changed their preferred payout account

Retry Logic

Resoon implements exponential backoff retry:

  • Retries: 3 attempts
  • Backoff: 1s → 2s → 4s (max 5s)
  • Timeout: 10 seconds per attempt

Best Practice: PayGear should respond with 200 OK quickly (< 5 seconds), process events asynchronously, and return { "received": true } immediately.

Important Notes

Authentication: All v1 endpoints require HMAC-SHA256 signature authentication. Get your API keys from the page.

Privacy: Resoon returns masked data to protect user privacy. Full IBANs, card numbers, and codemelli are never exposed via API.

Webhook Security: Always verify the X-Resoon-Signature header in webhook requests. Store your webhook secret securely.

Data Validation: Resoon validates Iranian national IDs (codemelli), mobile numbers, IBANs, and bank cards using proper checksum algorithms.

Error Codes

CodeStatusDescription
invalid_format422Validation failed
user_not_found404resoonId doesn't exist
duplicate_candidate200User already exists (returns existing ID with discrepancies)
unauthorized401Auth required
rate_limited429Too many requests
internal_error500Server error

Rate Limiting

Rate limits protect the API from abuse:

  • General: 100 requests/minute per IP
  • User Creation: 10 requests/minute per IP
  • Webhook Registration: 5 requests/hour

Support & Resources

For questions or issues with the API: