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.

Release files for reevit 0.10.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reevit 0.10.1
File Size Uploaded
reevit-0.10.1.tar.gz 18.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for reevit 0.10.1
File Interpreter ABI Platform
reevit-0.10.1-py3-none-any.whl Python 3 none any Details

Total release size: 36.3 kB

Release files / reevit-0.10.1.tar.gz

Download URL reevit-0.10.1.tar.gz
Size 18.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1739fa3540ce24d808d4f6c9c9657001bf16499ab77330b51d0f759da730a126
BLAKE2b-256 checksum
How to use checksums
56b559f51915f80415006370c590296424911ab9f4ee1ecb63424509224550f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.

Transparency log

Release files / reevit-0.10.1-py3-none-any.whl

Download URL reevit-0.10.1-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83e2216924ce413e78c0174a26e242b65ab0a32a1901c830b12cb3d47746d8d2
BLAKE2b-256 checksum
How to use checksums
3493823a22395b962f6f28b85951e122dc11e42aa459d34fbd40adb9443ceff4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.1 This release

2 release files

0.10.0

2 release files

0.9.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page