How Renegotiation Works

Overview

Renegotiation allows customers to modify the terms of an existing financed billing by creating a new payment plan with different installment amounts, due dates, or payment schedules. This feature is essential for managing customer payment difficulties and maintaining healthy business relationships.

This guide explains the technical process of renegotiation, webhook events, and how to properly integrate this functionality into your system.


What is Renegotiation?

Renegotiation is the process of modifying an active billing by:

  • Creating a new billing with updated payment terms
  • Canceling the original billing
  • Maintaining the same order (order status remains unchanged)

Common use cases for renegotiation:

  • Customer requests extended payment terms
  • Customer needs to adjust installment amounts
  • Customer requires different due dates
  • Payment restructuring scenarios

How the Renegotiation Process Works

Step-by-Step Flow

When a renegotiation is initiated, the following sequence occurs:

1. Order Status Remains Unchanged

The order status stays as finished. Renegotiation does not affect the order level - it only impacts the billing level.

Example:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "finished",  // Remains "finished" after renegotiation
  "billings": [
    // Will contain both old (canceled) and new (waiting_payment) billings
  ]
}

For more details, see the Order schema documentation.

2. Original Billing is Canceled

The existing billing is canceled with a specific cancelation reason: customer_renegotiation.

Webhook Event Triggered:

Event: billing.canceled
Entity: billing
Cancelation Reason: customer_renegotiation

Canceled Billing Example:

{
  "id": "a1b2c3d4-e5f6-4789-0abc-def123456789",
  "status": "canceled",
  "cancelation_reason": "customer_renegotiation",
  "order_id": "550e8400-e29b-41d4-a716-446655440000",
  "amount_cents": 299700,
  "installments": 24,
  "bills": [
    {
      "id": "bill-001",
      "status": "paid",
      "amount_cents": 12488
    },
    {
      "id": "bill-002",
      "status": "canceled",
      "amount_cents": 12488
    }
  ]
}

For more details about billing cancelation reasons, see the Billing schema documentation.

3. New Billing is Created

A new billing is created immediately with the updated payment terms. The new billing starts with status waiting_payment.

New Billing Properties:

  • Status: waiting_payment (transitions to active when first bill is paid)
  • origin_billing_id: References the original billing ID
  • New payment terms: Updated installment amounts, due dates, or payment schedule

New Billing Example:

{
  "id": "b2c3d4e5-f6a7-8901-2bcd-ef0123456789",
  "status": "waiting_payment",
  "origin_billing_id": "a1b2c3d4-e5f6-4789-0abc-def123456789",
  "order_id": "550e8400-e29b-41d4-a716-446655440000",
  "amount_cents": 287212,
  "installments": 18,
  "due_day": 15,
  "bills": [
    {
      "id": "bill-101",
      "status": "pending",
      "amount_cents": 15956,
      "due_date": "2024-04-15T23:59:59.999-03:00"
    },
    {
      "id": "bill-102",
      "status": "pending",
      "amount_cents": 15956,
      "due_date": "2024-05-15T23:59:59.999-03:00"
    }
  ]
}

4. Order Now Contains Both Billings

After renegotiation, the order will have:

  • 1 canceled billing (the original billing with cancelation_reason: customer_renegotiation)
  • 1 waiting_payment billing (the new billing with updated terms)

Complete Order Example After Renegotiation:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "finished",
  "customer_name": "João Silva",
  "customer_document": "12345678901",
  "customer_type": "person",
  "billings": [
    {
      "id": "a1b2c3d4-e5f6-4789-0abc-def123456789",
      "status": "canceled",
      "cancelation_reason": "customer_renegotiation",
      "amount_cents": 299700,
      "installments": 24,
      "origin_billing_id": null
    },
    {
      "id": "b2c3d4e5-f6a7-8901-2bcd-ef0123456789",
      "status": "waiting_payment",
      "cancelation_reason": null,
      "amount_cents": 287212,
      "installments": 18,
      "origin_billing_id": "a1b2c3d4-e5f6-4789-0abc-def123456789"
    }
  ]
}

Webhook Integration for Renegotiation

Important: No Order-Level Webhook

Renegotiation does NOT trigger an order webhook. The order status remains finished and no order event is fired.

Billing Webhook is Triggered

When a renegotiation occurs, you will receive a billing.canceled webhook with cancelation_reason: customer_renegotiation.

Setting Up the Webhook

To listen for renegotiation events, create a webhook for billing canceled events:

curl --location 'https://external-api-sandbox.lia.com.br/external_api/v1/webhooks' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_TOKEN' \
--data '{
  "name": "Billing Renegotiation Webhook",
  "url": "https://your-domain.com/webhooks/lia/billing-renegotiation",
  "entity": "billing",
  "events": ["canceled"],
  "verb": "post"
}'

For more information about webhooks, see the Webhooks documentation.

Webhook Payload Example

When a renegotiation happens, you will receive this payload:

{
  "event": "billing.canceled",
  "data": {
    "id": "a1b2c3d4-e5f6-4789-0abc-def123456789",
    "order_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "canceled",
    "cancelation_reason": "customer_renegotiation",
    "amount_cents": 299700,
    "amount_currency": "BRL",
    "due_day": 10,
    "installments": 24,
    "origin_billing_id": null,
    "finished_at": null,
    "created_at": "2024-01-15T10:30:00.000-03:00",
    "updated_at": "2024-03-20T14:45:00.000-03:00",
    "payment_configuration": {
      "id": "config-123",
      "payment_type": "financed",
      "financed_type": "bolepix"
    },
    "bills": [
      {
        "id": "bill-001",
        "status": "paid",
        "amount_cents": 12488
      },
      {
        "id": "bill-002",
        "status": "canceled",
        "amount_cents": 12488
      }
    ]
  }
}

Identifying Renegotiation Events

To specifically identify renegotiation (vs other billing cancellations), check the cancelation_reason field:

// Example webhook handler
function handleBillingCanceled(webhookData) {
  const billing = webhookData.data;
  
  if (billing.cancelation_reason === 'customer_renegotiation') {
    // This is a renegotiation event
    handleRenegotiation(billing);
  } else {
    // This is another type of cancellation
    handleOtherCancellation(billing);
  }
}

function handleRenegotiation(billing) {
  // 1. Fetch the new billing to get updated payment terms
  const order = fetchOrder(billing.order_id);
  const newBilling = order.billings.find(b => 
    b.origin_billing_id === billing.id && b.status === 'waiting_payment'
  );
  
  // 2. Update your internal systems with the new billing information
  updateInternalBilling(newBilling);
  
  // 3. Notify customer about the renegotiation
  notifyCustomer(billing.order_id, newBilling);
}

Renegotiation Constraints and Rules

Eligibility Requirements

Renegotiations can only be performed on billings that meet these criteria:

RequirementDescription
Billing StatusMust be active (cannot renegotiate waiting_payment, finished, or canceled billings)
Payment TypeMust be a financed payment type (upfront payments cannot be renegotiated)
Original AmountAll installments must meet the minimum installment amount requirement

Technical Limits

LimitValueDescription
Max Installments50Maximum number of installments allowed in a renegotiation
Max Start Date12 monthsMaximum time in the future for the first due date
Min Installment Amount5,000 cents (R$ 50.00)Minimum amount per installment

Monitoring Renegotiations

Fetching Order with Renegotiation History

To see all billings (including canceled and renegotiated ones):

curl --location 'https://external-api-sandbox.lia.com.br/external_api/v1/orders/ORDER_ID' \
--header 'Authorization: Bearer YOUR_API_TOKEN'

Identifying Renegotiation Chains

You can track renegotiation chains using the origin_billing_id field:

function getRenegotiationHistory(order) {
  const billings = order.billings;
  const chains = [];
  
  // Find root billings (no origin_billing_id)
  const rootBillings = billings.filter(b => !b.origin_billing_id);
  
  rootBillings.forEach(root => {
    const chain = [root];
    let current = root;
    
    // Follow the chain of renegotiations
    while (true) {
      const next = billings.find(b => b.origin_billing_id === current.id);
      if (!next) break;
      chain.push(next);
      current = next;
    }
    
    chains.push(chain);
  });
  
  return chains;
}

// Example usage
const order = await fetchOrder('ORDER_ID');
const renegotiationHistory = getRenegotiationHistory(order);

console.log(`Order has ${renegotiationHistory.length} billing chain(s)`);
renegotiationHistory.forEach((chain, index) => {
  console.log(`Chain ${index + 1}:`);
  chain.forEach(billing => {
    console.log(`  - ${billing.id} (${billing.status})`);
  });
});

Fetching Billing Details

To get details of a specific billing:

curl --location 'https://external-api-sandbox.lia.com.br/external_api/v1/billings/BILLING_ID' \
--header 'Authorization: Bearer YOUR_API_TOKEN'

For more information, see the Billings API documentation.


Complete Integration Example

Here’s a complete example of integrating renegotiation handling:

// 1. Set up webhook endpoint
app.post('/webhooks/lia/billing', async (req, res) => {
  const { event, data: billing } = req.body;
  
  // Acknowledge receipt immediately
  res.status(200).json({ received: true });
  
  // Process webhook asynchronously
  if (event === 'billing.canceled') {
    await handleBillingCanceled(billing);
  }
});

// 2. Handle billing canceled events
async function handleBillingCanceled(billing) {
  console.log(`Billing ${billing.id} was canceled`);
  console.log(`Reason: ${billing.cancelation_reason}`);
  
  // Check if this is a renegotiation
  if (billing.cancelation_reason === 'customer_renegotiation') {
    await handleRenegotiation(billing);
  } else {
    await handleRegularCancellation(billing);
  }
}

// 3. Handle renegotiation specifically
async function handleRenegotiation(oldBilling) {
  console.log(`Renegotiation detected for billing ${oldBilling.id}`);
  
  // Fetch updated order to get new billing
  const order = await fetchOrderFromAPI(oldBilling.order_id);
  
  // Find the new billing
  const newBilling = order.billings.find(b => 
    b.origin_billing_id === oldBilling.id && 
    b.status === 'waiting_payment'
  );
  
  if (!newBilling) {
    console.error('Could not find new billing after renegotiation');
    return;
  }
  
  console.log(`New billing created: ${newBilling.id}`);
  console.log(`New terms: ${newBilling.installments}x of R$ ${newBilling.amount_cents / 100 / newBilling.installments}`);
  
  // Update internal systems
  await updateInternalBillingStatus(oldBilling.id, 'renegotiated');
  await createInternalBillingRecord(newBilling);
  
  // Notify customer
  await notifyCustomerOfRenegotiation(order, oldBilling, newBilling);
}

// 4. Notify customer with new payment plan details
async function notifyCustomerOfRenegotiation(order, oldBilling, newBilling) {
  const customerEmail = order.email.address;
  const firstBill = newBilling.bills[0];
  
  const emailContent = {
    to: customerEmail,
    subject: 'Your Payment Plan Has Been Updated',
    body: `
      Hello ${order.customer_name},
      
      Your payment plan for order ${order.id} has been renegotiated.
      
      Previous Plan:
      - ${oldBilling.installments}x installments
      - Total: R$ ${(oldBilling.amount_cents / 100).toFixed(2)}
      
      New Plan:
      - ${newBilling.installments}x installments
      - Total: R$ ${(newBilling.amount_cents / 100).toFixed(2)}
      - First payment due: ${formatDate(firstBill.due_date)}
      - Amount per installment: R$ ${(firstBill.amount_cents / 100).toFixed(2)}
      
      Your first payment under the new plan is due on ${formatDate(firstBill.due_date)}.
      
      Thank you for your business!
    `
  };
  
  await sendEmail(emailContent);
}

// 5. Helper function to fetch order from API
async function fetchOrderFromAPI(orderId) {
  const response = await fetch(
    `https://external-api-sandbox.lia.com.br/external_api/v1/orders/${orderId}`,
    {
      headers: {
        'Authorization': `Bearer ${process.env.LIA_API_TOKEN}`
      }
    }
  );
  
  if (!response.ok) {
    throw new Error(`Failed to fetch order: ${response.statusText}`);
  }
  
  return await response.json();
}

Best Practices

1. Always Check cancelation_reason

Never assume all billing canceled events are the same. Always check the cancelation_reason field:

if (billing.status === 'canceled') {
  switch (billing.cancelation_reason) {
    case 'customer_renegotiation':
      handleRenegotiation(billing);
      break;
    case 'down_payment_not_paid':
      handleNonPayment(billing);
      break;
    case 'refunded_by_customer':
      handleRefund(billing);
      break;
    default:
      handleOtherCancellation(billing);
  }
}

2. Fetch Fresh Order Data

After receiving a renegotiation webhook, always fetch the latest order data to get the new billing:

// Don't rely solely on webhook data
const order = await fetchOrder(billing.order_id);
const newBilling = order.billings.find(b => b.origin_billing_id === billing.id);

3. Implement Idempotent Webhook Handlers

Webhooks may be delivered multiple times. Ensure your handlers are idempotent:

async function handleRenegotiation(billing) {
  // Check if we've already processed this renegotiation
  const alreadyProcessed = await checkIfProcessed(billing.id);
  if (alreadyProcessed) {
    console.log(`Renegotiation ${billing.id} already processed, skipping`);
    return;
  }
  
  // Process renegotiation
  await processRenegotiation(billing);
  
  // Mark as processed
  await markAsProcessed(billing.id);
}

4. Handle Errors Gracefully

Always implement error handling and retry logic:

async function handleRenegotiation(billing) {
  try {
    await processRenegotiation(billing);
  } catch (error) {
    console.error(`Error processing renegotiation: ${error.message}`);
    // Queue for retry
    await queueForRetry(billing.id);
  }
}

5. Log Renegotiation Events

Maintain detailed logs for troubleshooting:

await logEvent({
  type: 'renegotiation',
  old_billing_id: oldBilling.id,
  new_billing_id: newBilling.id,
  order_id: order.id,
  old_terms: {
    installments: oldBilling.installments,
    amount: oldBilling.amount_cents
  },
  new_terms: {
    installments: newBilling.installments,
    amount: newBilling.amount_cents
  },
  timestamp: new Date().toISOString()
});

Summary

Key Points to Remember

  1. Renegotiation happens at the billing level, not the order level
  2. Order status remains finished throughout the renegotiation process
  3. Original billing is canceled with cancelation_reason: customer_renegotiation
  4. New billing is created immediately with status waiting_payment
  5. Only one webhook is triggered: billing.canceled with the specific cancelation reason
  6. Always check the cancelation_reason field to identify renegotiation events
  7. Fetch fresh order data after receiving the webhook to get the new billing details

Support

If you have any questions about renegotiation or need assistance with your integration, please contact our support team.