Integration Architecture and Data Flow
The Easypost-Odoo integration employs a hybrid architecture that combines Odoo’s server-side logic with Easypost’s shipping API ecosystem. This design creates a bidirectional data flow that synchronizes shipping operations across both platforms. Your Odoo instance becomes the system of record for order data, while Easypost handles carrier communication and label generation. The architecture maintains data consistency without introducing complex distributed transaction management.
Odoo’s delivery carrier integration framework provides the foundation for the connection. You extend the delivery.carrier model with Easypost-specific functionality. This approach preserves Odoo’s native shipping workflow while adding Easypost’s multi-carrier capabilities. The integration intercepts shipping requests from sales orders and transfers them to Easypost’s API. Rate retrieval, label generation, and tracking synchronization all flow through this abstraction layer.
Data moves from Odoo to Easypost during the shipping label creation process. The integration sends order details, package dimensions, and destination addresses to Easypost’s API. Easypost processes this information through its carrier network and returns available shipping options. Your Odoo instance presents these rates to users through the standard delivery method selection interface. This pattern maintains user experience consistency while expanding carrier options.
Easypost pushes tracking events back to Odoo through webhook endpoints. You configure Easypost to send shipment status updates to a dedicated controller in your Odoo instance. This controller processes the webhook payloads and updates corresponding stock moves and delivery orders. The real-time tracking synchronization eliminates manual status checks and provides customers with automatic shipping notifications.
The architecture implements a fault-tolerant design with retry mechanisms for API calls. Failed Easypost requests trigger exponential backoff retries with jitter to prevent thundering herd problems. Idempotency keys ensure duplicate shipping requests don’t create multiple labels. This resilience pattern handles temporary network issues and Easypost API rate limits without data loss.
Data validation occurs at multiple points in the integration flow. Odoo validates address data against its partner records before sending to Easypost. Easypost performs additional address verification and correction using its carrier-specific validation rules. The integration compares these results and flags discrepancies for manual review. This multi-layer validation prevents shipping errors due to address problems.
Step-by-Step Configuration
Begin the configuration process by installing the base delivery module in Odoo 18. Navigate to the Apps menu and search for “delivery”. Install the “Delivery Costs” module if not already active. This module provides the carrier infrastructure that the Easypost integration extends. Verify the module functions correctly by creating a test delivery method through the shipping menu.
Create your Easypost developer account through their website. The sandbox environment provides a risk-free space for initial integration work. Generate API keys from your Easypost dashboard, making sure to copy both test and production keys. Store these keys securely as they grant access to your shipping account. The test key starts with “EZAK” while the production key begins with “EZPK”.
Implement the carrier module structure in Odoo. Create a new Python file named easypost_delivery.py in your custom module’s models directory. Define the main carrier class that inherits from delivery.carrier. This class encapsulates all Easypost-specific logic and overrides standard carrier methods. The class structure provides the framework for rate calculation, label generation, and tracking integration.
from odoo import models, fields, api
class EasypostCarrier(models.Model):
_inherit = 'delivery.carrier'
delivery_type = fields.Selection(
selection_add=[('easypost', 'Easypost')],
ondelete={'easypost': 'set default'}
)
easypost_test_key = fields.Char(string='Test API Key')
easypost_production_key = fields.Char(string='Production API Key')
easypost_default_package_type = fields.Selection([
('Card', 'Cardboard Box'),
('Pak', 'Package'),
('Tube', 'Tube'),
('Pallet', 'Pallet')
], string='Default Package Type')
Configure the API connection helper methods within your carrier class. These methods handle authentication, request formatting, and error processing. The connection logic must switch between test and production keys based on Odoo’s environment mode. Implement request signing with your API keys to authenticate with Easypost’s servers.
def _easypost_get_api_key(self):
"""Retrieve the appropriate API key based on environment"""
if self.env['ir.config_parameter'].get_param('easypost_use_production'):
return self.easypost_production_key
return self.eapost_test_key
def _easypost_make_request(self, endpoint, payload=None, method='POST'):
"""Execute API requests to Easypost with proper error handling"""
api_key = self._easypost_get_api_key()
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
# Request implementation with error handling
Set up the rate retrieval method that integrates with Odoo’s shipping cost calculation. This method converts Odoo’s delivery information into Easypost’s rate request format. It processes the API response and transforms the returned rates into Odoo’s expected format. The method must handle carrier-specific rate structures and currency conversions when applicable.
def easypost_rate_shipment(self, order):
"""Retrieve shipping rates from Easypost for the given order"""
# Convert order data to Easypost shipment format
shipment_data = self._prepare_easypost_shipment(order)
# Execute API call to Easypost rates endpoint
rates_response = self._easypost_make_request(
'/shipments',
shipment_data
)
# Parse response and convert to Odoo rate format
return self._parse_easypost_rates(rates_response, order)
Implement the label generation workflow that creates shipping labels when users confirm shipments. This method must handle the complete label creation process including insurance options and customs forms for international shipments. The method stores the generated label PDF and tracking number in the corresponding delivery order.
def easypost_send_shipping(self, pickings):
"""Generate shipping labels through Easypost API"""
result = []
for picking in pickings:
# Prepare shipment data from stock picking
shipment_data = self._prepare_easypost_shipment_from_picking(picking)
# Create shipment in Easypost
shipment = self._easypost_make_request('/shipments', shipment_data)
# Purchase label with selected service
label_result = self._easypost_make_request(
f'/shipments/{shipment["id"]}/buy',
{'rate': {'id': shipment['lowest_rate']['id']}}
)
# Store label data in picking
self._store_shipping_label(picking, label_result)
result.append({
'exact_price': label_result['rate']['price'],
'tracking_number': label_result['tracker']['id']
})
return result
Configure the webhook endpoint in Odoo that receives tracking updates from Easypost. Create a new controller that accepts POST requests from Easypost’s webhook system. This controller must verify request authenticity and process the tracking event payload. The implementation updates corresponding stock moves with current shipment status.
from odoo import http
from odoo.http import request
import json
class EasypostWebhook(http.Controller):
@http.route('/easypost/webhook', type='json', auth='public', methods=['POST'])
def handle_tracking_update(self):
"""Process tracking webhooks from Easypost"""
data = request.jsonrequest
tracking_number = data.get('result', {}).get('tracking_code')
status = data.get('result', {}).get('status')
# Find picking with this tracking number
picking = request.env['stock.picking'].search([
('carrier_tracking_ref', '=', tracking_number)
])
if picking:
picking.write({
'easypost_tracking_status': status,
'delivery_message': data.get('result', {}).get('message')
})
Set up the Easypost webhook configuration through their API or dashboard. Point the webhook URL to your Odoo instance’s /easypost/webhook endpoint. Configure which events Easypost should send, focusing on tracking updates and delivery exceptions. Test the webhook connection using Easypost’s webhook testing tools to verify end-to-end functionality.
Configure security settings for the webhook endpoint. Implement request validation to ensure only Easypost can submit webhook data. Add rate limiting to prevent abuse of the public endpoint. The webhook processor should handle duplicate events idempotently to maintain data consistency.
Data Mapping and Transformation
The integration transforms data between Odoo’s relational model and Easypost’s JSON API schema. This mapping process requires careful field-by-field conversion to maintain data integrity. Odoo’s partner addresses become Easypost address objects with specific formatting rules. Product dimensions and weights transform into Easypost parcel objects with unit conversions.
Map Odoo’s partner records to Easypost’s address format. The integration extracts name, company, street, city, state, zip, and country fields from Odoo’s res.partner model. It converts these into Easypost’s address validation request format. The system handles international address variations including province codes for Canada and VAT numbers for European shipments.
def _convert_partner_to_easypost_address(self, partner):
"""Transform Odoo partner record to Easypost address format"""
return {
'name': partner.name,
'company': partner.parent_id.name or partner.name,
'street1': partner.street,
'street2': partner.street2 or '',
'city': partner.city,
'state': partner.state_id.code or partner.state_id.name,
'zip': partner.zip,
'country': partner.country_id.code,
'phone': partner.phone or '',
'email': partner.email or ''
}
Transform product and packaging data into Easypost parcel objects. The integration calculates total shipment weight from stock move lines. It converts Odoo’s weight units (default kg) to Easypost’s expected ounces or grams. Package dimensions undergo similar unit conversion with validation for carrier-specific size restrictions.
def _prepare_easypost_parcel(self, picking):
"""Convert picking data to Easypost parcel format"""
total_weight = sum([
move.product_id.weight * move.product_qty
for move in picking.move_lines
])
# Convert kg to ounces for Easypost (1 kg = 35.274 ounces)
weight_ounces = total_weight * 35.274
return {
'length': self.package_length,
'width': self.package_width,
'height': self.package_height,
'weight': weight_ounces
}
Handle customs information transformation for international shipments. The integration extracts product HS codes, origin countries, and values from Odoo’s product catalog. It maps this data to Easypost’s customs item format for international commerce. The system includes description translation requirements for non-English customs forms.
Process carrier service level mapping between Odoo and Easypost. Odoo’s delivery method names correlate with Easypost’s carrier service codes. The integration maintains a mapping table that connects Odoo’s internal service identifiers with Easypost’s rate IDs. This mapping ensures users select valid service levels that both systems understand.
Transform tracking event data from Easypost’s webhook format to Odoo’s stock picking updates. The integration parses Easypost’s event descriptions and maps them to Odoo’s delivery status fields. It handles carrier-specific status messages and normalizes them into standard tracking states.
Manage currency and amount conversions between the financial systems. Odoo typically stores monetary values in company currency, while Easypost rates use USD. The integration performs real-time currency conversion using Odoo’s exchange rate tables. It applies appropriate rounding rules to prevent fractional cent discrepancies.
Address validation results require bidirectional data synchronization. When Easypost suggests address corrections, the integration presents these suggestions to users. Accepted corrections update both the Odoo partner record and the current shipment. This process maintains address consistency across future orders without automatic partner record modification.
Error Handling and Resilience
The integration encounters specific error conditions that require structured handling. Easypost API responses include detailed error codes that dictate appropriate recovery actions. Common errors include address validation failures, rate calculation exceptions, and label generation problems. Each error type demands specific handling logic to maintain system reliability.
Handle address validation errors that prevent shipment creation. Easypost returns “ADDRESS.VALIDATE.FAILURE” when addresses contain uncorrectable issues. The integration captures these errors and presents meaningful messages to Odoo users. It suggests specific address corrections and allows manual override when automated correction fails.
def _handle_address_error(self, error_response, partner):
"""Process Easypost address validation errors"""
error_code = error_response.get('error', {}).get('code')
if error_code == 'ADDRESS.VALIDATE.FAILURE':
suggestion = error_response.get('error', {}).get('suggestion')
if suggestion:
return {
'success': False,
'warning_message': f"Address validation failed. Suggested address: {suggestion}",
'suggested_address': suggestion
}
return {
'success': False,
'error_message': 'Address could not be validated for shipping'
}
Manage rate calculation failures that occur during checkout. Easypost returns “RATE.ERROR” when no carriers service the specified route. The integration handles these cases by falling back to configured default rates. It logs the specific failure reason for administrative review while maintaining user workflow continuity.
Implement retry logic for temporary API failures and rate limits. Easypost enforces rate limits that vary by account tier. The integration detects “429 Too Many Requests” responses and implements exponential backoff. Failed requests due to network timeouts trigger automatic retries with jitter to prevent synchronized retry storms.
def _easypost_make_request_with_retry(self, endpoint, payload, max_retries=3):
"""Execute API request with retry logic for temporary failures"""
for attempt in range(max_retries):
try:
response = self._easypost_make_request(endpoint, payload)
return response
except requests.exceptions.ConnectionError as e:
if attempt == max_retries - 1:
raise e
sleep_time = (2 ** attempt) + random.random()
time.sleep(sleep_time)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
retry_after = int(e.response.headers.get('Retry-After', 60))
time.sleep(retry_after)
else:
raise e
Process label generation failures that prevent shipment confirmation. Easypost returns “SHIPMENT.INVALID.PARAMETERS” when package dimensions exceed carrier limits. The integration validates dimensions proactively but handles edge case failures gracefully. It provides specific guidance about carrier constraints and suggests alternative packaging options.
Handle webhook processing failures that disrupt tracking synchronization. The integration implements dead letter queue patterns for failed webhook processing. Unprocessable webhook payloads move to a quarantine area for manual inspection. This prevents data corruption while maintaining system availability during error conditions.
Manage authentication failures that require administrative intervention. Easypost returns “AUTHENTICATION.ERROR” when API keys expire or become invalid. The integration detects these errors and escalates them through Odoo’s notification system. It maintains fallback shipping methods to prevent complete shipping workflow failure.
Implement comprehensive logging for all integration operations. The system records request/response pairs for troubleshooting and audit purposes. Log entries include correlation IDs that track operations across both systems. This logging strategy accelerates problem resolution during production incidents.
Testing and Validation
Establish a comprehensive testing strategy that covers all integration scenarios. The testing approach combines unit tests for individual components with integration tests for complete workflows. Create test cases that validate both successful operations and error conditions. The test suite must verify data consistency across Odoo and Easypost systems.
Develop unit tests for the core data transformation functions. Test address conversion with international format variations. Verify parcel weight calculations with mixed product units. Validate currency conversion accuracy with edge case amounts. These unit tests ensure data integrity before API communication occurs.
def test_partner_to_address_conversion(self):
"""Test Odoo partner to Easypost address conversion"""
partner = self.env['res.partner'].create({
'name': 'Test Customer',
'street': '123 Main St',
'city': 'San Francisco',
'state_id': self.state_ca.id,
'country_id': self.country_us.id,
'zip': '94105'
})
carrier = self.env['delivery.carrier'].create({
'name': 'Easypost Test',
'delivery_type': 'easypost',
'easypost_test_key': 'test_key'
})
address_data = carrier._convert_partner_to_easypost_address(partner)
self.assertEqual(address_data['city'], 'San Francisco')
self.assertEqual(address_data['state'], 'CA')
Create integration tests that execute complete shipping workflows. Test rate retrieval with realistic order data across multiple carrier services. Verify label generation with various package types and insurance options. Validate tracking synchronization through simulated webhook payloads. These tests confirm end-to-end system functionality.
Implement negative testing scenarios that verify error handling. Test rate calculation with invalid address combinations. Attempt label generation with overweight packages. Verify system behavior when Easypost API becomes unavailable. These tests ensure graceful degradation during failure conditions.
Develop performance tests that validate system behavior under load. Measure rate calculation response times with concurrent user requests. Test webhook processing capacity during high-volume shipping periods. Verify that the integration maintains responsiveness during Easypost API rate limiting.
Create validation checklists for each implementation phase. The configuration checklist verifies API connectivity and authentication. The data mapping checklist confirms field transformation accuracy. The operational checklist validates complete order-to-shipment workflow functionality. These checklists ensure thorough implementation quality assurance.
Prepare test data that covers business-specific shipping scenarios. Include domestic and international address variations. Create products with different weight categories and dimensions. Set up customers with special shipping requirements. This comprehensive test data validates the integration against real-world usage patterns.
Establish automated testing pipelines that execute on code changes. Configure continuous integration to run the test suite after each commit. Set up environment-specific test configurations for development, staging, and production. This automation prevents regression errors during system evolution.
Security Considerations
The integration handles sensitive data that demands robust security protection. Shipping information includes customer addresses, product details, and commercial values. The security implementation must safeguard this data throughout the integration lifecycle. Follow defense-in-depth principles with multiple security layers.
Protect API keys that grant access to your Easypost account. Store keys in Odoo’s parameter system with appropriate access controls. Never commit keys to version control systems or expose them in client-side code. Implement key rotation procedures that periodically regenerate access credentials.
Secure webhook endpoints that receive data from Easypost. Validate webhook request signatures to ensure authenticity. Easypost signs webhook requests with your API secret - verify this signature before processing. Implement replay attack protection by checking event timestamps and maintaining processed event IDs.
def verify_webhook_signature(request):
"""Verify Easypost webhook request signature"""
received_signature = request.headers.get('X-Easypost-Signature')
expected_signature = hmac.new(
settings.EASYPOST_WEBHOOK_SECRET.encode(),
request.body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(received_signature, expected_signature)
Encrypt sensitive data stored within Odoo databases. Apply field-level encryption to API keys and authentication tokens. Use Odoo’s built-in encryption capabilities or external encryption services. Ensure encryption keys management follows organizational security policies.
Implement access controls that restrict integration configuration. Limit carrier configuration permissions to authorized shipping administrators. Apply role-based access to shipping operations and rate management. Audit permission assignments regularly to maintain principle of least privilege.
Secure data transmission between Odoo and Easypost APIs. Enforce TLS 1.2 or higher for all API communications. Validate certificate authenticity to prevent man-in-the-middle attacks. Configure appropriate cipher suites that balance security and compatibility.
Protect against injection attacks in data transformation logic. Sanitize all data received from Easypost webhooks before database insertion. Validate data types and ranges for all API request parameters. Use parameterized queries when storing Easypost data in custom tables.
Establish security monitoring for integration activities. Log authentication attempts and API usage patterns. Monitor for anomalous shipping activity that might indicate security breaches. Implement alerting for repeated authentication failures or unexpected rate limit violations.
Maintain compliance with data protection regulations. Shipping data often contains personal information subject to GDPR, CCPA, or other privacy laws. Implement data retention policies that automatically purge old shipping records. Provide data export and deletion capabilities for privacy request fulfillment.
Performance Optimization
The integration introduces performance considerations that impact user experience. Rate calculation during checkout requires low latency to maintain sales conversion. Label generation must complete quickly to support efficient warehouse operations. Tracking synchronization should occur near real-time for customer communication.
Optimize rate calculation performance through strategic caching. Store frequently used rate combinations in Odoo’s cache system. Implement cache invalidation rules that refresh rates when products, addresses, or carriers change. This caching strategy reduces Easypost API calls while maintaining rate accuracy.
def get_cached_rates(self, order):
"""Retrieve rates from cache or calculate fresh if expired"""
cache_key = f"easypost_rates_{order.id}_{order.write_date.timestamp()}"
cached_rates = self.env.cache.get(cache_key)
if cached_rates:
return cached_rates
# Calculate fresh rates
new_rates = self._retrieve_fresh_rates(order)
# Cache for 15 minutes
self.env.cache.set(cache_key, new_rates, timeout=900)
return new_rates
Implement batch operations for high-volume shipping scenarios. Process multiple shipments in single API calls when Easypost supports batch endpoints. Queue non-time-sensitive operations like tracking synchronization for background processing. This approach minimizes API round trips and improves overall throughput.
Optimize database queries that support integration operations. Add appropriate indexes to tables storing Easypost tracking numbers and shipment references. Use Odoo’s read groups for analytics instead of individual record queries. Monitor query performance and adjust indexing strategies as data volumes grow.
Manage API rate limits through request throttling and queueing. Distribute API calls evenly across time to avoid rate limit violations. Implement priority queues that ensure time-sensitive operations receive preferential treatment. Monitor usage against your Easypost plan limits and scale capacity before hitting constraints.
Reduce payload size through data compression and field selection. Request only necessary fields from Easypost APIs using their field selection parameters. Compress large API responses before caching to reduce memory usage. This optimization becomes particularly important with high-volume shipping operations.
Implement connection pooling for API communications. Reuse HTTP connections to Easypost instead of establishing new connections for each request. Configure appropriate timeouts that balance responsiveness with resource utilization. Monitor connection health and automatically recycle problematic connections.
Optimize webhook processing throughput through asynchronous design. Process webhook payloads in background jobs instead of blocking the response. Use bulk database operations when updating multiple records from webhook data. This approach maintains webhook endpoint responsiveness during traffic spikes.
Monitor integration performance with appropriate metrics. Track rate calculation response times and label generation success rates. Measure API usage efficiency and cache hit ratios. Set up alerts for performance degradation that might indicate underlying problems. Use this data to guide continuous optimization efforts.