FactPulse SDK Ruby
Official Ruby client for the FactPulse API - French electronic invoicing.
Features
- Factur-X: Generation and validation of electronic invoices (MINIMUM, BASIC, EN16931, EXTENDED profiles)
- Chorus Pro: Integration with the French public invoicing platform
- AFNOR PDP/PA: Submission of flows compliant with XP Z12-013 standard
- Electronic signature: PDF signing (PAdES-B-B, PAdES-B-T, PAdES-B-LT)
- Thin HTTP wrapper: Generic
postandgetmethods with automatic JWT auth and polling
Installation
gem install factpulse
Or in your Gemfile:
gem 'factpulse'
Quick Start
require 'factpulse'
require 'base64'
# Create the client
client = FactPulse::Client.new(
email: '[email protected]',
password: 'your_password',
client_uid: 'your-client-uuid' # From dashboard: Configuration > Clients
)
# Read your source PDF
pdf_b64 = Base64.strict_encode64(File.binread('source_invoice.pdf'))
# Generate Factur-X and submit to PDP in one call
result = client.post('processing/invoices/submit-complete-async',
invoiceData: {
number: 'INV-2025-001',
supplier: {
siret: '12345678901234',
iban: 'FR7630001007941234567890185',
routing_address: '12345678901234'
},
recipient: {
siret: '98765432109876',
routing_address: '98765432109876'
},
lines: [
{
description: 'Consulting services',
quantity: 10,
unitPrice: 100.0,
vatRate: 20.0
}
]
},
sourcePdf: pdf_b64,
profile: 'EN16931',
destination: { type: 'afnor' }
)
# PDF is in result['content'] (auto-polled, auto-decoded)
File.binwrite('facturx_invoice.pdf', result['content'])
puts "Flow ID: #{result['afnorResult']['flowId']}"
API Methods
The SDK provides two generic methods that map directly to API endpoints:
# POST /api/v1/{path}
result = client.post('path/to/endpoint', key1: value1, key2: value2)
# GET /api/v1/{path}
result = client.get('path/to/endpoint', param1: value1)
Common Endpoints
| Endpoint | Method | Description |
|---|---|---|
processing/invoices/submit-complete-async |
POST | Generate Factur-X + submit to PDP |
processing/generate-invoice |
POST | Generate Factur-X XML or PDF |
processing/validate-xml |
POST | Validate Factur-X XML |
processing/validate-facturx-pdf |
POST | Validate Factur-X PDF |
processing/sign-pdf |
POST | Sign PDF with certificate |
afnor/flow/v1/flows |
POST | Submit flow to AFNOR PDP |
afnor/incoming-flows/{flow_id} |
GET | Get incoming invoice |
chorus-pro/factures/soumettre |
POST | Submit to Chorus Pro |
Webhooks
Instead of polling, you can receive results via webhook by adding callbackUrl:
result = client.post('processing/invoices/submit-complete-async',
invoiceData: invoice_data,
sourcePdf: pdf_b64,
destination: { type: 'afnor' },
callbackUrl: 'https://your-server.com/webhook/factpulse',
webhookMode: 'INLINE' # or 'DOWNLOAD_URL'
)
task_id = result['taskId']
# Result will be POSTed to your webhook URL
Webhook Receiver Example (Sinatra)
require 'sinatra'
require 'json'
require 'openssl'
WEBHOOK_SECRET = 'your-shared-secret'
def verify_signature(payload, signature)
return false unless signature&.start_with?('sha256=')
expected = OpenSSL::HMAC.hexdigest('SHA256', WEBHOOK_SECRET, payload)
Rack::Utils.secure_compare(expected, signature[7..])
end
post '/webhook/factpulse' do
payload = request.body.read
signature = request.env['HTTP_X_WEBHOOK_SIGNATURE']
unless verify_signature(payload, signature)
halt 401, { error: 'Invalid signature' }.to_json
end
event = JSON.parse(payload)
event_type = event['event_type']
data = event['data']
case event_type
when 'submission.completed'
flow_id = data.dig('afnorResult', 'flowId')
puts "Invoice submitted: #{flow_id}"
when 'submission.failed'
puts "Submission failed: #{data['error']}"
end
content_type :json
{ status: 'received' }.to_json
end
Webhook Event Types
| Event | Description |
|---|---|
generation.completed |
Factur-X generated successfully |
generation.failed |
Generation failed |
validation.completed |
Validation passed |
validation.failed |
Validation failed |
signature.completed |
PDF signed |
submission.completed |
Submitted to PDP/Chorus |
submission.failed |
Submission failed |
Zero-Storage Mode
Pass PDP credentials directly in the request (no server-side storage):
result = client.post('processing/invoices/submit-complete-async',
invoiceData: invoice_data,
sourcePdf: pdf_b64,
destination: {
type: 'afnor',
flowServiceUrl: 'https://api.pdp.example.com/flow/v1',
tokenUrl: 'https://auth.pdp.example.com/oauth/token',
clientId: 'your_pdp_client_id',
clientSecret: 'your_pdp_client_secret'
}
)
Error Handling
require 'factpulse'
begin
result = client.post('processing/validate-xml', xmlContent: xml_string)
rescue FactPulse::Error => e
puts "Error: #{e.message}"
puts "Status code: #{e.status_code}"
puts "Details: #{e.details}"
end
Resources
- API Documentation: https://factpulse.fr/api/facturation/documentation
- Support: [email protected]
License
MIT License - Copyright (c) 2025 FactPulse