Skip to main content

Overview

Webhooks allow you to receive HTTP callbacks when events occur in your Bipa account. Instead of polling the API, you can react to events in real-time.

Setting up webhooks

  1. Go to DevelopersWebhooks in your console
  2. Click Add endpoint
  3. Enter your webhook URL (must be HTTPS)
  4. Select the events you want to receive
  5. Copy the signing secret for verification

Webhook payload structure

All webhook events follow this structure:
{
  "id": "evt_a1b2c3d4e5f6",
  "type": "pix.payment.completed",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "object": {
      "id": "pix_pay_xyz789",
      "customer_id": "cus_a1b2c3d4e5f6",
      "amount_cents": 100000,
      "status": "completed"
      // ... full object data
    }
  }
}
FieldTypeDescription
idstringUnique identifier for this event
typestringThe event type (e.g., pix.payment.completed)
created_atstringISO 8601 timestamp of when the event occurred
data.objectobjectThe full object associated with the event

Event types

Customer events

EventDescription
customer.createdA new customer was created
customer.updatedCustomer information was updated

Pix events

EventDescription
pix.payment.receivedIncoming Pix payment received (cash-in)
pix.payment.completedOutgoing Pix payment completed (cash-out)
pix.payment.failedPix payment failed

Trade events

EventDescription
trade.completedA trade was successfully executed
trade.failedA trade failed to execute

On-chain events

EventDescription
onchain.deposit.pendingDeposit detected, awaiting confirmations
onchain.deposit.confirmedDeposit confirmed and credited
onchain.withdrawal.completedWithdrawal successfully sent
onchain.withdrawal.failedWithdrawal failed

Lightning events

EventDescription
lightning.invoice.paidLightning invoice was paid
lightning.payment.completedOutgoing Lightning payment completed
lightning.payment.failedLightning payment failed

Verifying webhook signatures

Every webhook request includes a signature in the X-Bipa-Signature header. Always verify this signature to ensure the request came from Bipa. The signature is an HMAC-SHA256 hash of the request body using your webhook signing secret.
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler
@app.post("/webhook")
def handle_webhook(request):
    payload = request.body
    signature = request.headers.get("X-Bipa-Signature")

    if not verify_webhook(payload, signature, WEBHOOK_SECRET):
        return Response(status=401)

    event = json.loads(payload)
    # Process the event
    return Response(status=200)
Never process webhook events without verifying the signature. Attackers could forge requests to your endpoint.

Handling webhooks

Respond quickly

Return a 2xx status code within 30 seconds. If your processing takes longer, acknowledge the webhook first and process asynchronously.
@app.post("/webhook")
async def handle_webhook(request):
    event = await request.json()

    # Queue for async processing
    await queue.enqueue(process_event, event)

    # Respond immediately
    return Response(status=200)

Handle duplicates

Webhook events may be delivered more than once. Use the id field to deduplicate:
def handle_event(event):
    # Check if we've already processed this event
    if redis.get(f"webhook:{event['id']}"):
        return  # Already processed

    # Process the event
    process_event(event)

    # Mark as processed (with TTL)
    redis.setex(f"webhook:{event['id']}", 86400, "1")

Implement idempotency

Design your handlers to produce the same result even if called multiple times with the same event.

Retry policy

If your endpoint returns a non-2xx status or times out, Bipa will retry the webhook:
AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
58 hours
624 hours
After 6 failed attempts, the webhook is marked as failed. You can view failed webhooks and manually retry them in the console.

Testing webhooks

Using the console

  1. Go to DevelopersWebhooks
  2. Select your endpoint
  3. Click Send test event
  4. Choose an event type to send

Local development

Use a tunneling service like ngrok to expose your local server:
ngrok http 3000
Then add the ngrok URL as a webhook endpoint in test mode.

Example: Processing a Pix payment

@app.post("/webhook")
def handle_webhook(request):
    # Verify signature
    if not verify_webhook(request.body, request.headers.get("X-Bipa-Signature"), SECRET):
        return Response(status=401)

    event = request.json()

    match event["type"]:
        case "pix.payment.received":
            payment = event["data"]["object"]

            # Credit the user's account in your system
            user = get_user_by_customer_id(payment["customer_id"])
            credit_user_balance(user, payment["amount_cents"])

            # Send notification
            send_notification(user, f"Received R$ {payment['amount_cents'] / 100:.2f} via Pix")

        case "pix.payment.failed":
            payment = event["data"]["object"]

            # Handle the failure
            notify_payment_failure(payment)

    return Response(status=200)

Webhook logs

View all webhook deliveries in DevelopersWebhooksLogs. Each log entry shows:
  • Event type and ID
  • Delivery status (success/failed)
  • Response status code
  • Response time
  • Request and response bodies