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.


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.9.1.tar.gz (14.8 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.9.1-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for reevit-0.9.1.tar.gz
Algorithm Hash digest
SHA256 1c1c007fef3ddfb9b615edd8baebdef0a20e801fb1772af421128da32261014a
MD5 ff90c828a869b4d3b4799699c66c2773
BLAKE2b-256 e2e461015e7dcc89a98b6bbb7593d80664f12e68ad2a3658d4517c4bcab95f3a

See more details on using hashes here.

Provenance

The following attestation bundles were made for reevit-0.9.1.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.9.1-py3-none-any.whl.

File metadata

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

File hashes

Hashes for reevit-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8810843781c2c348c9cedda52af0e118108ee92b002c6761adfc8e5ae3336c87
MD5 7766930cd60ebe2778747ac14d4da11c
BLAKE2b-256 d4c698e94051a721d16ed9aa6b7405af435f9bbe89acf6ee42503fea507c3e5e

See more details on using hashes here.

Provenance

The following attestation bundles were made for reevit-0.9.1-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