---
updatedAt: 2025-12-16T12:35:00.000Z
---

Fetch the complete documentation index at: https://www.payretailers.dev/llms.txt. Use this file to discover all available pages before exploring further.

# Webhooks and Notifications

# Webhooks and Notifications

## What are Webhooks?

Webhooks are HTTP POST notifications sent to your server in real-time when subscription or payment events occur. They allow you to react automatically to changes in subscription status, payment processing, and other important events without polling the API.

## Configuration

### Webhook URL

You must provide a `notificationUrl` when creating a subscription. This URL will receive all webhook notifications for that subscription and its payments.

**Requirements**:

* Must be publicly accessible (HTTPS)
* Must accept POST requests
* Must return 200 OK responses
* Should respond quickly (process asynchronously if needed)

**Example**:

```json
{
  "notificationUrl": "https://your-domain.com/webhooks/subscriptions"
}
```

### Authentication

The WebhookSender service handles webhook delivery, including:

* Retry logic for failed deliveries
* Rate limiting
* Webhook signature generation
* Delivery confirmation

Your webhook endpoint should validate the webhook signature if provided by the WebhookSender service.

## Event Types

### Subscription Events

#### subscription\_activation

Triggered when a subscription is activated (user authorizes the subscription).

**When it occurs**:

* User authorizes subscription via Journey 1 (push notification)
* User authorizes subscription via Journey 2 (QR code scan)

**Payload**:

```json
{
  "eventType": "subscription_activation",
  "eventId": "event-guid-123",
  "eventDate": "2025-01-15T10:30:00Z",
  "entityId": "subscription-guid",
  "status": "ACTIVE",
  "statusDate": "2025-01-15T10:30:00Z",
  "message": null,
  "errorCode": null,
  "errorMessage": null,
  "pspReference": null,
  "end2EndId": null,
  "additionalData": []
}
```

#### subscription\_cancellation

Triggered when a subscription is cancelled.

**When it occurs**:

* Merchant cancels subscription via API
* User cancels subscription via their bank/app
* Subscription expires automatically

**Payload**:

```json
{
  "eventType": "subscription_cancellation",
  "eventId": "event-guid-124",
  "eventDate": "2025-01-20T14:00:00Z",
  "entityId": "subscription-guid",
  "status": "CANCELLED",
  "statusDate": "2025-01-20T14:00:00Z",
  "message": "Cancelled by user",
  "errorCode": null,
  "errorMessage": null,
  "pspReference": null,
  "end2EndId": null,
  "additionalData": []
}
```

### Payment Events

#### subscription.payment (PAID)

Triggered when a payment is successfully processed.

**When it occurs**:

* Payment is successfully processed by the payment provider
* Retry attempt succeeds

**Payload**:

```json
{
  "eventType": "subscription.payment",
  "eventId": "event-guid-125",
  "eventDate": "2025-01-15T10:35:00Z",
  "entityId": "payment-guid",
  "status": "PAID",
  "statusDate": "2025-01-15T10:35:00Z",
  "message": null,
  "errorCode": null,
  "errorMessage": null,
  "pspReference": "123456-7890-acasfsa-23424",
  "end2EndId": "E-1323434-acac-3214324-adasd",
  "additionalData": []
}
```

#### subscription.payment (FAILED)

Triggered when a payment fails.

**When it occurs**:

* Payment fails due to insufficient funds
* Payment fails due to account closure
* Payment fails due to authorization revocation
* Payment fails for any other reason

**Payload**:

```json
{
  "eventType": "subscription.payment",
  "eventId": "event-guid-126",
  "eventDate": "2025-01-15T10:40:00Z",
  "entityId": "payment-guid",
  "status": "FAILED",
  "statusDate": "2025-01-15T10:40:00Z",
  "message": null,
  "errorCode": "INSUFFICIENT_FUNDS",
  "errorMessage": "Insufficient funds in account",
  "pspReference": "123456-7890-acasfsa-23425",
  "end2EndId": null,
  "additionalData": [
    {
      "key": "retryCount",
      "value": "1"
    }
  ]
}
```

#### subscription.payment (CANCELLED)

Triggered when a payment is cancelled.

**When it occurs**:

* Merchant cancels payment via API
* Payment is cancelled due to timing restrictions (e.g., PIX cancellation deadline)
* Subscription is cancelled before payment is processed

**Payload**:

```json
{
  "eventType": "subscription.payment",
  "eventId": "event-guid-127",
  "eventDate": "2025-01-15T11:00:00Z",
  "entityId": "payment-guid",
  "status": "CANCELLED",
  "statusDate": "2025-01-15T11:00:00Z",
  "message": "Cancelled by merchant",
  "errorCode": null,
  "errorMessage": null,
  "pspReference": null,
  "end2EndId": null,
  "additionalData": []
}
```

#### subscription.payment\_schedule

Triggered when the next payment is automatically scheduled.

**When it occurs**:

* System automatically schedules the next payment (48 hours before due date)
* Only sent if automatic scheduling is enabled

**Payload**:

```json
{
  "eventType": "subscription.payment_schedule",
  "eventId": "event-guid-128",
  "eventDate": "2025-01-13T10:00:00Z",
  "entityId": "payment-guid",
  "status": "PENDING",
  "statusDate": "2025-01-13T10:00:00Z",
  "message": null,
  "errorCode": null,
  "errorMessage": null,
  "pspReference": null,
  "end2EndId": null,
  "additionalData": [
    {
      "key": "scheduledDate",
      "value": "2025-01-15T10:00:00Z"
    }
  ]
}
```

#### subscription.payment\_cancellation

Triggered when a payment cancellation is requested.

**When it occurs**:

* Merchant requests payment cancellation
* System processes payment cancellation

**Payload**:

```json
{
  "eventType": "subscription.payment_cancellation",
  "eventId": "event-guid-129",
  "eventDate": "2025-01-15T12:00:00Z",
  "entityId": "payment-guid",
  "status": "CANCELLED",
  "statusDate": "2025-01-15T12:00:00Z",
  "message": "Payment cancellation requested",
  "errorCode": null,
  "errorMessage": null,
  "pspReference": null,
  "end2EndId": null,
  "additionalData": []
}
```

## Payload Structure

All webhook payloads follow this structure:

```json
{
  "eventType": "string",
  "eventId": "string",
  "eventDate": "ISO8601 datetime",
  "entityId": "string",
  "status": "string",
  "statusDate": "ISO8601 datetime",
  "message": "string | null",
  "errorCode": "string | null",
  "errorMessage": "string | null",
  "pspReference": "string | null",
  "end2EndId": "string | null",
  "additionalData": [
    {
      "key": "string",
      "value": "string"
    }
  ]
}
```

### Field Descriptions

* **`eventType`**: Type of event (e.g., `subscription_activation`, `subscription.payment`)
* **`eventId`**: Unique identifier for this event (use for idempotency)
* **`eventDate`**: UTC timestamp when the event occurred
* **`entityId`**: ID of the entity that triggered the event (SubscriptionPayment.Id or Subscription.Id)
* **`status`**: Current status of the entity (e.g., `ACTIVE`, `PAID`, `FAILED`)
* **`statusDate`**: UTC timestamp when the status changed
* **`message`**: Optional message (e.g., cancellation reason)
* **`errorCode`**: Error code if applicable (e.g., `INSUFFICIENT_FUNDS`)
* **`errorMessage`**: Human-readable error message
* **`pspReference`**: Payment Service Provider reference (for payments)
* **`end2EndId`**: End-to-End ID (PIX-specific identifier)
* **`additionalData`**: Additional context data (key-value pairs)

## Common Error Codes

### Payment Errors

* **`INSUFFICIENT_FUNDS`**: Account has insufficient funds
* **`REJECTED_BY_BANK`**: Payment rejected by the bank
* **`CANCELLED_BY_USER`**: User cancelled the payment
* **`ACCOUNT_CLOSED`**: User's account is closed
* **`AUTHORIZATION_REVOKED`**: User revoked subscription authorization
* **`PAYMENT_EXPIRED`**: Payment expired before processing
* **`INVALID_ACCOUNT`**: Invalid account information
* **`LIMIT_EXCEEDED`**: Transaction limit exceeded

### Subscription Errors

* **`AUTHORIZATION_REJECTED`**: User rejected subscription authorization
* **`INVALID_CUSTOMER`**: Customer information is invalid
* **`CONFIGURATION_ERROR`**: Subscription configuration error

## Webhook Processing

### Receiving Webhooks

1. **Receive POST Request**: Your endpoint receives the webhook payload
2. **Validate Signature** (if implemented): Verify the webhook is from PayRetailers
3. **Check Idempotency**: Use `eventId` to ensure you haven't processed this event
4. **Process Event**: Update your system based on the event
5. **Respond 200 OK**: Return success response quickly

### Idempotency

Always use the `eventId` field to ensure idempotency. Store processed event IDs and check before processing:

```python
# Example idempotency check
def process_webhook(payload):
    event_id = payload['eventId']
    
    # Check if already processed
    if is_event_processed(event_id):
        return 200  # Already processed, return success
    
    # Process event
    process_event(payload)
    
    # Mark as processed
    mark_event_processed(event_id)
    
    return 200
```

### Response Requirements

Your webhook endpoint must:

* **Respond quickly**: Return 200 OK within a few seconds
* **Process asynchronously**: Do heavy processing in background jobs
* **Handle errors gracefully**: Return appropriate HTTP status codes

**Response Codes**:

* **200 OK**: Webhook received and processed successfully
* **400 Bad Request**: Invalid payload (webhook will not be retried)
* **500 Internal Server Error**: Processing error (webhook will be retried)

### Retry Logic

The WebhookSender service automatically retries failed webhooks:

* Retries occur if your endpoint doesn't return 200 OK
* Retry intervals increase exponentially
* Maximum retry attempts are configurable
* Failed webhooks are logged for investigation

## Best Practices

### Idempotency

* Always check `eventId` before processing
* Store processed event IDs
* Handle duplicate events gracefully

### Fast Response

* Respond with 200 OK immediately
* Process events asynchronously
* Use background jobs for heavy processing

### Error Handling

* Log all webhook events
* Handle errors gracefully
* Return appropriate HTTP status codes
* Monitor webhook delivery failures

### Validation

* Validate `entityId` exists in your system
* Verify event structure
* Check required fields

### Security

* Validate webhook signatures (if provided)
* Use HTTPS for webhook URLs
* Implement rate limiting
* Monitor for suspicious activity

### Monitoring

* Log all webhook events
* Track processing times
* Monitor error rates
* Alert on delivery failures

## Example Webhook Handler

```python
from flask import Flask, request, jsonify
import logging

app = Flask(__name__)
logger = logging.getLogger(__name__)

# Store processed event IDs (use Redis in production)
processed_events = set()

@app.route('/webhooks/subscriptions', methods=['POST'])
def handle_webhook():
    try:
        payload = request.json
        event_id = payload.get('eventId')
        event_type = payload.get('eventType')
        entity_id = payload.get('entityId')
        status = payload.get('status')
        
        # Idempotency check
        if event_id in processed_events:
            logger.info(f"Event {event_id} already processed")
            return jsonify({'status': 'ok'}), 200
        
        # Process event based on type
        if event_type == 'subscription_activation':
            handle_subscription_activation(payload)
        elif event_type == 'subscription.payment':
            handle_payment_event(payload)
        elif event_type == 'subscription_cancellation':
            handle_subscription_cancellation(payload)
        
        # Mark as processed
        processed_events.add(event_id)
        
        return jsonify({'status': 'ok'}), 200
        
    except Exception as e:
        logger.error(f"Error processing webhook: {e}")
        return jsonify({'error': 'Internal error'}), 500

def handle_subscription_activation(payload):
    subscription_id = payload['entityId']
    # Update subscription status in your system
    logger.info(f"Subscription {subscription_id} activated")

def handle_payment_event(payload):
    payment_id = payload['entityId']
    status = payload['status']
    
    if status == 'PAID':
        # Update payment status, send confirmation email, etc.
        logger.info(f"Payment {payment_id} paid")
    elif status == 'FAILED':
        # Handle failed payment, notify customer, etc.
        error_code = payload.get('errorCode')
        logger.warning(f"Payment {payment_id} failed: {error_code}")

def handle_subscription_cancellation(payload):
    subscription_id = payload['entityId']
    # Update subscription status, handle cancellation
    logger.info(f"Subscription {subscription_id} cancelled")
```

## Next Steps

* **[Subscription Concepts](subscription-concepts.md)**: Review subscription entities and statuses
* **[Automatic Scheduling](automatic-scheduling.md)**: Understand payment scheduling
* **[Retry Policies](retry-policies.md)**: Learn about retry handling