Reseller Payment Webhook

Transfer SMS units from parent company to child company after receiving payment. This endpoint is used in the RoyceTalk reseller program for parent-to-child balance transfers.

For Reseller Parents Only

This endpoint is exclusively for parent companies in the reseller hierarchy. When a child company pays you via M-Pesa or another payment method, you call this endpoint to transfer SMS units from your balance to theirs.

Not a reseller parent? This endpoint won't work for you. Contact support to set up a reseller account.

ENDPOINT

POST /api/v1/sms-api/webhooks/payment/

AUTHENTICATION

Bearer Token (Parent API Key)

How It Works

1

Child Pays Parent

Your child company sends payment to your M-Pesa paybill or bank account (e.g., KES 1,100 to buy 2,000 SMS units at KES 0.55 per unit).

2

You Receive Payment Callback

M-Pesa (or your payment provider) sends a callback to your payment system with transaction details: amount, reference, account name.

3

Call This Webhook

Your payment system calls this endpoint with your parent API key, child's account name, payment amount, and M-Pesa reference.

4

SMS Units Transferred

RoyceTalk transfers SMS units from your balance to your child's balance. You get a detailed response with profit calculation and new balances.

Request Parameters

ParameterTypeRequiredDescription
account_namestringYesChild company's account name (max 255 characters)
amountdecimalYesPayment amount received (e.g., "1100.00"). Must be greater than 0.
payment_referencestringRecommendedUnique payment reference (e.g., M-Pesa transaction ID). Used for duplicate prevention. Max 100 characters, alphanumeric plus dash/underscore only.
currencystringOptionalPayment currency (ISO 4217 code). Defaults to parent company's currency. Must match parent's currency if provided.

Important: Always Provide payment_reference

While technically optional, we highly recommend always providing a uniquepayment_reference (e.g., M-Pesa transaction ID). This prevents duplicate processing if your system accidentally calls this endpoint twice with the same payment.

Code Examples

Replace YOUR_PARENT_API_KEY_HERE with your actual parent company API key.

import requests
from datetime import datetime

# Your parent company API credentials
PARENT_API_KEY = "YOUR_PARENT_API_KEY_HERE"
API_URL = "https://roycetalk.com/api/v1/sms-api/webhooks/payment/"

def process_child_payment(child_account_name, amount, mpesa_reference):
    """
    Process payment from child company and transfer SMS units.
    
    Args:
        child_account_name: Child company's account name
        amount: Payment amount received from child
        mpesa_reference: M-Pesa transaction reference
    
    Returns:
        dict: Transfer result with balance updates
    """
    
    # Prepare the request
    headers = {
        "Authorization": f"Bearer {PARENT_API_KEY}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "account_name": child_account_name,
        "amount": str(amount),
        "payment_reference": mpesa_reference,
        "currency": "KES"
    }
    
    try:
        # Send the request
        response = requests.post(API_URL, json=payload, headers=headers)
        
        # Check response status
        if response.status_code == 200:
            result = response.json()
            
            print("✓ Payment processed successfully!")
            print(f"Transfer Reference: {result['data']['transfer_reference']}")
            print(f"SMS Units Transferred: {result['data']['transfer_details']['sms_units_transferred']}")
            print(f"Parent Balance After: {result['data']['parent_company']['balance_after']}")
            print(f"Child Balance After: {result['data']['child_company']['balance_after']}")
            print(f"Parent Profit: KES {result['data']['parent_company']['profit']}")
            
            return result
            
        elif response.status_code == 409:
            # Duplicate payment reference
            error = response.json()
            print(f"⚠ Duplicate payment: {error['errors'][0]['message']}")
            print(f"Original transaction: {error['errors'][0]['details']['existing_transfer']}")
            return None
            
        elif response.status_code == 404:
            # Child company not found
            error = response.json()
            print(f"✗ Child company not found: {error['errors'][0]['message']}")
            return None
            
        elif response.status_code == 400:
            # Validation error or insufficient balance
            error = response.json()
            print(f"✗ Error: {error['errors'][0]['message']}")
            if 'details' in error['errors'][0]:
                print(f"Details: {error['errors'][0]['details']}")
            return None
            
        else:
            print(f"✗ Unexpected error: {response.status_code}")
            print(response.text)
            return None
            
    except requests.exceptions.RequestException as e:
        print(f"✗ Request failed: {str(e)}")
        return None


# Example usage
if __name__ == "__main__":
    # Process a payment from child company
    result = process_child_payment(
        child_account_name="child_company_abc",
        amount=1100.00,
        mpesa_reference="MPESA_ABC123XYZ"
    )
    
    if result:
        # Payment processed successfully
        # You can store the transfer_reference in your database
        transfer_ref = result['data']['transfer_reference']
        print(f"\n✓ Store this transfer reference: {transfer_ref}")

Success Response

200 OK
{ "success": true, "data": { "transfer_reference": "PAY-A1B2C3D4E5F6", "payment_reference": "MPESA_ABC123XYZ", "parent_company": { "name": "Parent Reseller Ltd", "account_name": "parent_account_001", "balance_before": 50000, "balance_after": 48000, "sms_rate": "0.50", "cost_incurred": "1000.00", "revenue_received": "1100.00", "profit": "100.00" }, "child_company": { "name": "Child Company ABC", "account_name": "child_company_abc", "balance_before": 10000, "balance_after": 12000, "buying_rate": "0.55", "reseller_level": 1 }, "transfer_details": { "payment_amount": "1100.00", "currency": "KES", "sms_units_transferred": 2000, "buying_rate_applied": "0.55", "calculation": "1100.00 / 0.55 = 2000 units", "timestamp": "2025-12-19T10:30:00+03:00" }, "transactions": { "parent_transaction_id": "uuid-parent-transaction", "child_transaction_id": "uuid-child-transaction" }, "processing_time_seconds": 0.18 }, "message": "Payment processed successfully. Transferred 2000 SMS units to Child Company ABC", "meta": { "timestamp": "2025-12-19T10:30:00.180000+00:00", "api_version": "v1" } }

Understanding the Response

  • profit: Your revenue (what child paid) minus your cost (units × your rate). This is your profit from the transaction.
  • calculation: Shows how SMS units were calculated: amount ÷ child's buying rate.
  • balance_before/after: Both parent and child balances before and after the transfer.
  • transfer_reference: Unique reference for this transfer. Store this in your database for reconciliation.

Error Responses

409 ConflictDuplicate Payment Reference
{ "success": false, "data": {}, "message": "Payment reference 'MPESA_ABC123' has already been processed", "errors": [ { "code": "DUPLICATE_PAYMENT_REFERENCE", "message": "Payment reference 'MPESA_ABC123' has already been processed", "details": { "payment_reference": "MPESA_ABC123", "existing_transfer": { "transaction_id": "uuid-existing-transaction", "sms_units": 2000, "balance_after": 12000, "created_at": "2025-12-19T09:00:00+03:00" }, "recommendation": "Use a unique payment reference for each transaction" } } ] }

This payment reference has already been used. The response includes details of the original transaction. This is normal if you retry a failed request - the original transfer succeeded, so no double-charging occurs.

404 Not FoundChild Company Not Found
{ "success": false, "message": "Child company with account name 'xyz' not found or inactive", "errors": [ { "code": "CHILD_COMPANY_NOT_FOUND", "message": "Child company with account name 'xyz' not found or inactive", "details": { "account_name": "xyz", "parent_company": "Parent Reseller Ltd", "parent_account": "parent_001" } } ] }

The child company either doesn't exist, is inactive, or doesn't belong to you. Verify the account_name is correct.

400 Bad RequestInsufficient Parent Balance
{ "success": false, "message": "Parent company has insufficient SMS balance", "errors": [ { "code": "INSUFFICIENT_PARENT_BALANCE", "message": "Parent company has insufficient SMS balance", "details": { "required_units": 5000, "available_units": 2000, "shortfall": 3000, "parent_company": "Parent Reseller Ltd", "recommendation": "Top up parent company balance before retrying" } } ] }

You don't have enough SMS units to fulfill this transfer. Top up your parent account balance and retry.

400 Bad RequestValidation Error
{ "success": false, "message": "Invalid webhook payload", "errors": [ { "code": "VALIDATION_ERROR", "message": "Invalid webhook payload", "details": { "amount": ["Amount must be greater than 0"], "payment_reference": ["Payment reference contains invalid characters"] } } ] }

One or more request parameters are invalid. Check the details field for specific validation errors.

Best Practices

Always Use Unique Payment References

Use M-Pesa transaction IDs or other unique identifiers as the payment_reference. This prevents accidental double-processing and provides a clear audit trail.

Store Transfer References

Save the transfer_reference from successful responses in your database. This helps with reconciliation and troubleshooting.

Handle 409 Gracefully

A 409 Conflict response means the payment was already processed successfully. Don't treat this as an error - use the returned existing_transfer details to confirm the original transfer.

Log Profit for Accounting

The response includes your profit from each transaction. Log this for financial reporting and reconciliation.

Don't Retry Failed Payments Without Investigation

If you get a 400 or 500 error, investigate the cause before retrying. Check logs, verify balances, and confirm the child account name is correct.

Monitor Your Parent Balance

Set up alerts when your parent balance drops below a threshold. This prevents "insufficient balance" errors when processing child payments.

Common Integration Scenarios

Scenario 1: M-Pesa Paybill Integration

Most common scenario: Child companies pay you via your M-Pesa paybill.

1. Child sends money to your M-Pesa paybill

2. M-Pesa sends callback to your server with transaction details

3. Extract: amount, M-Pesa reference, account number (child's account name)

4. Call this webhook with extracted data

5. SMS units automatically transferred to child

Scenario 2: Manual Bank Transfer

Child companies pay via bank transfer or other manual payment methods.

1. Child sends bank transfer with their account name in reference

2. You receive bank notification (email/SMS/statement)

3. Manually call this webhook via your admin panel or script

4. Provide: child account name, amount, bank reference

5. SMS units transferred to child

Need Help?

Contact our support team for assistance with reseller integrations.

[email protected]