Skip to main content

Reevit Python SDK

The official Python SDK for Reevit — a unified payment orchestration platform for Africa.

PyPI version Python versions License: MIT

Installation

pip install reevit==0.9.1

Quick Start

from reevit import Reevit

client = Reevit(api_key="pfk_live_xxx", org_id="org_123")

# Create a payment
try:
    payment = client.payments.create_intent({
        "amount": 5000,  # 50.00 GHS
        "currency": "GHS",
        "method": "momo",
        "country": "GH",
        "customer_id": "cust_123",
        "metadata": {
            "order_id": "12345"
        }
    }, idempotency_key="order_12345")
    print(f"Payment created: {payment['id']}")
except Exception as e:
    print(f"Error: {e}")

# List payments
payments = client.payments.list()
print(payments)

Server-created checkout sessions

Create checkout sessions on your server, then pass session["session_secret"] to a browser SDK — @reevit/react, @reevit/vue, or @reevit/svelte — to render the checkout UI.

session = client.checkout_sessions.create(
    {
        "amount": 5000,
        "currency": "GHS",
        "method": "mobile_money",
        "country": "GH",
    },
    idempotency_key="order_12345",
)

Idempotency

Pass idempotency_key to safely retry intent creation without duplicates.

payment = client.payments.create_intent(
    {
        "amount": 5000,
        "currency": "GHS",
        "method": "momo",
        "country": "GH",
    },
    idempotency_key="order_12345",
)

Features

  • Payments: Create intents, update intents, confirm, confirm intent, cancel, retry, refund, stats
  • Connections: Manage PSP integrations, validation, labels, status, audit
  • Subscriptions: Manage recurring billing lifecycle
  • Fraud: Configure fraud rules
  • Customers / Payment Links / Checkout Sessions / Webhooks / Routing Rules / Invoices: Additional backend services

org_id is supported directly on the client. Omitting it for authenticated requests still works for backward compatibility, but that mode is deprecated.

To fetch every connected PSP rather than one page:

connections = client.connections.list_all(mode="live", status="active")
page = client.connections.list_page(limit=50, offset=0)

Webhook Verification

Reevit sends webhooks to notify your application of payment events. Always verify webhook signatures.

Understanding Webhooks

There are two types of webhooks in Reevit:

  1. Inbound Webhooks (PSP → Reevit): Webhooks from payment providers (Paystack, Flutterwave, etc.) to Reevit. Configure these in the PSP dashboard. Reevit handles them automatically.

  2. Outbound Webhooks (Reevit → Your App): Webhooks from Reevit to your application. Configure in Reevit Dashboard and create a handler in your app.

Signature Format

  • Header: X-Reevit-Signature: sha256=<hex-signature>
  • Signature: HMAC-SHA256(request_body, signing_secret)

Getting Your Signing Secret

  1. Go to Reevit Dashboard > Developers > Webhooks
  2. Configure your webhook endpoint URL
  3. Copy the signing secret (starts with whsec_)
  4. Set environment variable: REEVIT_WEBHOOK_SECRET=whsec_xxx...

Flask Webhook Handler

The SDK ships a constant-time verifier — verify_webhook_signature(payload, signature, secret) — so you do not have to reimplement HMAC. Pass the raw request body (not parsed-and-reserialized JSON), the X-Reevit-Signature header, and your signing secret.

import os
import logging
from dataclasses import dataclass
from typing import Optional, Dict, Any
from flask import Flask, request, jsonify
from reevit import verify_webhook_signature

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class PaymentData:
    id: str
    status: str
    amount: int
    currency: str
    provider: str
    customer_id: Optional[str] = None
    metadata: Optional[Dict[str, str]] = None

@dataclass
class SubscriptionData:
    id: str
    customer_id: str
    plan_id: str
    status: str
    amount: int
    currency: str
    interval: str
    next_renewal_at: Optional[str] = None

@app.route('/webhooks/reevit', methods=['POST'])
def webhook():
    payload = request.get_data()  # raw bytes — do not re-serialize
    signature = request.headers.get('X-Reevit-Signature', '')
    secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')

    # Verify signature (required in production)
    if not verify_webhook_signature(payload, signature, secret):
        logger.warning('[Webhook] Invalid signature')
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.get_json()
    event_type = event.get('type')
    event_id = event.get('id')
    
    logger.info(f'[Webhook] Received: {event_type} ({event_id})')
    
    # Handle different event types
    if event_type == 'reevit.webhook.test':
        logger.info(f'[Webhook] Test received: {event.get("message")}')
    
    # Payment events
    elif event_type == 'payment.succeeded':
        data = PaymentData(**event.get('data', {}))
        handle_payment_succeeded(data)
    
    elif event_type == 'payment.failed':
        data = PaymentData(**event.get('data', {}))
        handle_payment_failed(data)
    
    elif event_type == 'payment.refunded':
        data = PaymentData(**event.get('data', {}))
        handle_payment_refunded(data)
    
    elif event_type == 'payment.pending':
        data = PaymentData(**event.get('data', {}))
        logger.info(f'[Webhook] Payment pending: {data.id}')
    
    # Subscription events
    elif event_type == 'subscription.created':
        data = SubscriptionData(**event.get('data', {}))
        handle_subscription_created(data)
    
    elif event_type == 'subscription.renewed':
        data = SubscriptionData(**event.get('data', {}))
        handle_subscription_renewed(data)
    
    elif event_type == 'subscription.canceled':
        data = SubscriptionData(**event.get('data', {}))
        handle_subscription_canceled(data)
    
    else:
        logger.info(f'[Webhook] Unhandled event: {event_type}')
    
    return jsonify({'received': True})

# Payment handlers
def handle_payment_succeeded(data: PaymentData):
    order_id = data.metadata.get('order_id') if data.metadata else None
    logger.info(f'[Webhook] Payment succeeded: {data.id} for order {order_id}')
    
    # TODO: Implement your business logic
    # - Update order status to "paid"
    # - Send confirmation email to customer
    # - Trigger fulfillment process

def handle_payment_failed(data: PaymentData):
    logger.info(f'[Webhook] Payment failed: {data.id}')
    
    # TODO: Implement your business logic
    # - Update order status to "payment_failed"
    # - Send notification to customer
    # - Allow retry

def handle_payment_refunded(data: PaymentData):
    order_id = data.metadata.get('order_id') if data.metadata else None
    logger.info(f'[Webhook] Payment refunded: {data.id} for order {order_id}')
    
    # TODO: Implement your business logic
    # - Update order status to "refunded"
    # - Restore inventory if applicable

# Subscription handlers
def handle_subscription_created(data: SubscriptionData):
    logger.info(f'[Webhook] Subscription created: {data.id} for customer {data.customer_id}')
    
    # TODO: Implement your business logic
    # - Grant access to subscription features
    # - Send welcome email

def handle_subscription_renewed(data: SubscriptionData):
    logger.info(f'[Webhook] Subscription renewed: {data.id}')
    
    # TODO: Implement your business logic
    # - Extend access period
    # - Send renewal confirmation

def handle_subscription_canceled(data: SubscriptionData):
    logger.info(f'[Webhook] Subscription canceled: {data.id}')
    
    # TODO: Implement your business logic
    # - Revoke access at end of billing period
    # - Send cancellation confirmation

if __name__ == '__main__':
    app.run(port=8080)

Django Webhook Handler

# views.py
import json
import os
import logging
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from reevit import verify_webhook_signature

logger = logging.getLogger(__name__)

@csrf_exempt
@require_POST
def reevit_webhook(request):
    payload = request.body  # raw bytes — do not re-serialize
    signature = request.headers.get('X-Reevit-Signature', '')
    secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
    
    if not verify_webhook_signature(payload, signature, secret):
        return JsonResponse({'error': 'Invalid signature'}, status=401)
    
    event = json.loads(payload)
    event_type = event.get('type')
    
    logger.info(f'[Webhook] Received: {event_type}')
    
    if event_type == 'payment.succeeded':
        data = event.get('data', {})
        order_id = data.get('metadata', {}).get('order_id')
        # Fulfill order, send confirmation email
        logger.info(f'Payment succeeded for order {order_id}')
    
    elif event_type == 'payment.failed':
        # Notify customer, allow retry
        pass
    
    elif event_type == 'subscription.renewed':
        # Extend access
        pass
    
    elif event_type == 'subscription.canceled':
        # Revoke access
        pass
    
    return JsonResponse({'received': True})

FastAPI Webhook Handler

from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict
import os
import logging
from reevit import verify_webhook_signature

app = FastAPI()
logger = logging.getLogger(__name__)

class PaymentData(BaseModel):
    id: str
    status: str
    amount: int
    currency: str
    provider: str
    customer_id: Optional[str] = None
    metadata: Optional[Dict[str, str]] = None

class SubscriptionData(BaseModel):
    id: str
    customer_id: str
    plan_id: str
    status: str
    amount: int
    currency: str
    interval: str
    next_renewal_at: Optional[str] = None

@app.post('/webhooks/reevit')
async def webhook(request: Request):
    payload = await request.body()  # raw bytes — do not re-serialize
    signature = request.headers.get('X-Reevit-Signature', '')
    secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
    
    if not verify_webhook_signature(payload, signature, secret):
        raise HTTPException(status_code=401, detail='Invalid signature')
    
    event = await request.json()
    event_type = event.get('type')
    
    logger.info(f'[Webhook] Received: {event_type}')
    
    # Payment events
    if event_type == 'payment.succeeded':
        data = PaymentData(**event.get('data', {}))
        order_id = data.metadata.get('order_id') if data.metadata else None
        logger.info(f'Payment succeeded: {data.id} for order {order_id}')
        # Fulfill order, send confirmation email
    
    elif event_type == 'payment.failed':
        # Notify customer, allow retry
        pass
    
    # Subscription events
    elif event_type == 'subscription.renewed':
        data = SubscriptionData(**event.get('data', {}))
        logger.info(f'Subscription renewed: {data.id}')
        # Extend access
    
    elif event_type == 'subscription.canceled':
        data = SubscriptionData(**event.get('data', {}))
        logger.info(f'Subscription canceled: {data.id}')
        # Revoke access
    
    return {'received': True}

Supported PSPs

Provider Countries Payment Methods
Paystack NG, GH, ZA, KE Card, Mobile Money, Bank Transfer
Flutterwave NG, GH, KE, ZA + Card, Mobile Money, Bank Transfer
Hubtel GH Mobile Money
Stripe Global (50+) Card, Apple Pay, Google Pay
Monnify NG Card, Bank Transfer, USSD
M-Pesa KE, TZ Mobile Money (STK Push)

Release Notes

v0.9.1

  • Added verify_webhook_signature / sign_webhook_payload helpers (constant-time HMAC-SHA256 verification of the X-Reevit-Signature header)
  • Version is now sourced from reevit._version and sent as the X-Reevit-Client-Version header

v0.9.0

  • Added server-created checkout sessions
  • Version alignment across all Reevit SDKs
  • Updated documentation and webhook examples
  • Added support for Apple Pay and Google Pay
  • Updated supported PSPs and payment methods documentation

Environment Variables

export REEVIT_API_KEY=pfk_live_xxx
export REEVIT_ORG_ID=org_xxx
export REEVIT_WEBHOOK_SECRET=whsec_xxx  # Get from Dashboard > Developers > Webhooks

Support

License

MIT License - see LICENSE for details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reevit-0.10.0.tar.gz (16.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reevit-0.10.0-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

File details

Details for the file reevit-0.10.0.tar.gz.

File metadata

  • Download URL: reevit-0.10.0.tar.gz
  • Upload date:
  • Size: 16.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for reevit-0.10.0.tar.gz
Algorithm Hash digest
SHA256 3c976141d1ab1c888a9d760df117ef3d81e85b50a85d91648b8bea20f45a6715
MD5 1106ac9d1c3c6e59e64e51230a2002aa
BLAKE2b-256 b6ef07c240ffc1233570a71433367bba1af3769a436b451be2f243ed92224f25

See more details on using hashes here.

Provenance

The following attestation bundles were made for reevit-0.10.0.tar.gz:

Publisher: publish.yml on Reevit-Platform/python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reevit-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: reevit-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 16.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for reevit-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1dc9de7147da5d5d5bc463d2eeacd79c8eeec0f332a48b8a71f8035f16e350df
MD5 18a8aa5068a83409ef25d35fd06289cf
BLAKE2b-256 b75e4500fecb1cbeabb5072b6c1569087893cc8052e9ee0c35bd00f56de208e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for reevit-0.10.0-py3-none-any.whl:

Publisher: publish.yml on Reevit-Platform/python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page