Package Exports
- @elyonar/einvoice-js
- @elyonar/einvoice-js/webhooks
Readme
@elyonar/einvoice-js
Official TypeScript/JavaScript SDK for the E-Invoice Nigeria platform — streamlining electronic invoicing, tax compliance, and B2B2B partner integrations.
Features
- Full API coverage — Invoices, sellers, buyers, billing, subscriptions, organizations, users, API keys, webhooks, invitations, and roles (117 methods across 10 services)
- Type-safe — First-class TypeScript support with comprehensive type definitions
- Resilient — Automatic retry with exponential backoff and jitter for 5xx and 429 errors
- Rate limit aware — Respects
Retry-Afterheaders, throwsEInvoiceRateLimitErrorwhen exhausted - Secure webhooks — HMAC-SHA256 signature verification with timing-safe comparison and replay protection
- B2B2B partner support —
forOrganization(),forApiKey(), andcreateOrganizationWithApiKey()for multi-org management - Zero dependencies — Uses native
fetch,crypto, andAbortController - Dual builds — ESM and CommonJS
- Modern — Node.js 18+ with environment auto-detection from API key prefix
Requirements
- Node.js 18.0.0 or higher — The SDK uses native
fetchandcrypto.timingSafeEqual, which require Node 18+. - An E-Invoice API key — Get one from your E-Invoice dashboard or from your platform administrator.
Installation
npm install @elyonar/einvoice-jsGetting Started
This walks you through the minimum steps to submit your first invoice. Every SDK method returns an ApiResponse<T> envelope — your data is always in .data.
Step 1 — Initialize the client
import { EInvoice } from '@elyonar/einvoice-js';
const client = new EInvoice({ apiKey: 'sk_test_your_key_here' });The SDK auto-detects your environment from the key prefix:
sk_test_*→ Sandbox (https://local-api.einvoice.ng) — no real tax submissionssk_live_*→ Production (https://gateway.einvoice.ng) — real tax authority submissions
Step 2 — Register a seller (your business)
Before creating invoices, you need a seller (the entity issuing the invoice):
const seller = await client.sellers.create({
partyName: 'Acme Nigeria Ltd',
email: 'billing@acme.ng',
phoneNumber: '+2348012345678',
taxNumber: '12345678-0001',
postalAddress: {
line1: '123 Commerce Street',
city: 'Lagos',
state: 'Lagos',
country: 'NG',
},
});
console.log(seller.data.id); // 'sel_abc123' — save thisStep 3 — Register a buyer (your customer)
const buyer = await client.buyers.create({
partyName: 'John Doe Enterprises',
email: 'john@example.com',
phoneNumber: '+2349087654321',
postalAddress: {
line1: '456 Market Road',
city: 'Abuja',
country: 'NG',
},
});
console.log(buyer.data.id); // 'buy_xyz789' — save thisStep 4 — Create and submit an invoice
// Create a draft invoice
const invoice = await client.invoices.create({
sellerId: seller.data.id,
buyerId: buyer.data.id,
invoiceNumber: 'INV-2026-001',
invoiceDate: '2026-04-19',
lineItems: [
{
description: 'Consulting services — April 2026',
quantity: 10,
unitPrice: 50000,
taxPercent: 7.5,
hsnCode: '9983', // HSN code for professional services
unitCode: 'HUR', // Hours
},
],
});
// Submit to tax authority (irreversible in production)
const submission = await client.invoices.submit(invoice.data.id);
console.log(submission.data.jobId); // 'job_abc123'
console.log(submission.data.status); // 'queued'Step 5 — Check the status
// Local DB status (fast)
const status = await client.invoices.getStatus(invoice.data.id);
console.log(status.data.status); // 'queued' | 'pending' | 'accepted' | 'rejected' | 'failed'
// Real-time query to tax authority (slower, use sparingly)
const liveStatus = await client.invoices.queryStatus(invoice.data.id);Authentication
API Keys
Every request is authenticated with an API key passed as a Bearer token. The SDK handles this automatically — just pass your key to the constructor.
Authorization: Bearer sk_test_your_key_hereKey Types
| Prefix | Environment | Base URL | Behavior |
|---|---|---|---|
sk_test_* |
Sandbox | https://local-api.einvoice.ng |
Invoices are NOT submitted to the tax authority. Safe for testing. |
sk_live_* |
Production | https://gateway.einvoice.ng |
Invoices ARE submitted to the tax authority. Irreversible. |
Key Scoping
API keys are scoped to the organization that created them. A key can only access data (invoices, sellers, buyers, billing, etc.) belonging to its own organization. It cannot access data from other organizations, including parent or sibling orgs.
This is critical for B2B2B partners — see the B2B2B Partner Guide below.
Configuration
const client = new EInvoice({
apiKey: 'sk_test_...', // Required
organizationId: 'org_uuid', // Required for org-scoped services
options: {
baseUrl: 'https://custom.api.com', // Override auto-detected URL
timeout: 60000, // Request timeout in ms (default: 30000)
retry: {
maxAttempts: 5, // Max retries for 5xx/429 (default: 3)
baseDelay: 2000, // Base delay in ms (default: 1000)
},
headers: {
'X-Custom-Header': 'value', // Additional headers on every request
},
},
});| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
— | Required. Your API key. Determines environment automatically. |
organizationId |
string |
— | Required for org-scoped services (webhooks, API keys, users, invitations, roles). |
options.baseUrl |
string |
Auto-detected | Override the API gateway URL. |
options.timeout |
number |
30000 |
Request timeout in milliseconds. |
options.retry.maxAttempts |
number |
3 |
Max retry attempts for 5xx and 429 errors. 4xx errors are never retried. |
options.retry.baseDelay |
number |
1000 |
Base delay between retries in ms. Actual delay uses exponential backoff with ±10% jitter. |
options.headers |
Record<string, string> |
— | Additional headers included in every request. |
Organization-Scoped Services
Some services manage resources that belong to a specific organization. These require organizationId in the constructor:
client.webhooks— webhook endpoints and deliveriesclient.apiKeys— API key lifecycleclient.users— user management within an orgclient.invitations— team invitations (exceptverifyandacceptwhich are public)client.roles— custom role and RBAC management
If you call an org-scoped method without organizationId, the SDK throws EInvoiceConfigError with a clear message.
// This will throw EInvoiceConfigError:
const client = new EInvoice({ apiKey: 'sk_test_...' });
await client.webhooks.list(); // Error: organizationId is required
// This works:
const client = new EInvoice({ apiKey: 'sk_test_...', organizationId: 'org_123' });
await client.webhooks.list(); // OKResponse Format
Every SDK method returns ApiResponse<T>:
interface ApiResponse<T> {
meta: {
statusCode: number;
success: boolean;
message: string;
errors: Array<{ field?: string; message: string; code?: string }>;
timestamp: string;
pagination?: {
total: number;
page: number;
pageSize: number;
totalPages: number;
hasNext: boolean;
hasPrevious: boolean;
};
};
data: T;
}Always access your data via .data:
const response = await client.invoices.create({ ... });
const invoice = response.data; // The actual invoice object
const success = response.meta.success; // truePagination
List methods accept page and pageSize parameters. Pagination info is in meta.pagination:
const page1 = await client.invoices.list({ page: 1, pageSize: 20 });
console.log(page1.meta.pagination.total); // 85
console.log(page1.meta.pagination.totalPages); // 5
console.log(page1.meta.pagination.hasNext); // true
// Get next page
const page2 = await client.invoices.list({ page: 2, pageSize: 20 });API Reference
Invoices — client.invoices
Full invoice lifecycle: create, validate, submit, track, download, and cancel.
// CRUD
client.invoices.create(params) // Create a draft invoice
client.invoices.get(id) // Get invoice by ID
client.invoices.list(params?) // List with filters (status, date range, seller, buyer)
client.invoices.update(id, params) // Update a draft (sellerId/buyerId/invoiceNumber are immutable)
client.invoices.delete(id) // Delete a draft invoice
// Tax Authority Submission
client.invoices.submit(id) // Submit to tax authority (irreversible, returns jobId)
client.invoices.batchSubmit(ids) // Submit 1–100 invoices at once
client.invoices.retry(id) // Retry a failed submission
client.invoices.cancel(id, params) // Cancel an accepted invoice (creates credit note)
// Status
client.invoices.getStatus(id) // Status from local DB (fast)
client.invoices.queryStatus(id) // Real-time status from tax authority (slower)
client.invoices.validate(params) // Validate invoice data without creating
// Download & Analytics
client.invoices.download(id, format?) // Download as 'pdf' or 'xml' (default: pdf)
client.invoices.getStatistics(params?) // Invoice counts and totals by status
// Reference Data
client.invoices.getHsnCodes(params?) // Search HSN/tax classification codes
client.invoices.getHsnCategories() // List HSN code categories
// Invoice Resources (jurisdiction-agnostic)
client.invoices.getResources() // Bundled resources (countries, currencies, tax categories, etc.)
client.invoices.getResourceByType(type) // Single resource collection by type
client.invoices.lookupTaxId(taxId) // Look up taxpayer by tax ID (TIN/CNPJ/PIN)
client.invoices.validateReference(params) // Validate an invoice reference from the tax authority
client.invoices.submitTaxReport(id, params) // Submit post-payment tax report for an invoiceCreateInvoiceParams (required fields):
{
sellerId: string; // Seller ID from sellers.create()
buyerId: string; // Buyer ID from buyers.create()
invoiceNumber: string; // Your invoice number (unique per org)
invoiceDate: string; // ISO date: '2026-04-19'
lineItems: [{
description: string; // Line item description
quantity: number; // Quantity
unitPrice: number; // Price per unit (in smallest currency unit or decimal)
taxPercent: number; // Tax rate as percentage (e.g. 7.5 for 7.5%)
hsnCode: string; // HSN/SAC code for tax classification
unitCode: string; // UBL unit code (e.g. 'EA', 'KGM', 'HUR')
}];
}Invoice status lifecycle: draft → queued → pending → accepted | rejected | failed — and accepted → cancelled via cancel()
Sellers — client.sellers
Register and manage the entities that issue invoices.
client.sellers.create(params) // Register a seller
client.sellers.get(id) // Get seller by ID
client.sellers.list(params?) // List sellers (paginated)
client.sellers.update(id, params) // Update seller details
client.sellers.delete(id) // Delete seller
client.sellers.bulkDelete(ids) // Bulk delete multiple sellers
client.sellers.verifyTin(id) // Initiate TIN verification with tax authority
client.sellers.getVerificationStatus(id) // Check TIN verification progress
client.sellers.search(query) // Search by name or TINCreateSellerParams (required fields):
{
partyName: string; // Business name
email: string; // Contact email
phoneNumber: string; // Contact phone (with country code)
taxNumber: string; // Tax Identification Number
postalAddress: {
line1: string; // Street address
city: string; // City
country: string; // ISO country code (e.g. 'NG')
line2?: string; // Additional address line
state?: string; // State/province
postalCode?: string; // Postal/ZIP code
};
partyType?: string; // 'company' (default), 'individual', 'partnership', etc.
}Buyers — client.buyers
Register and manage the entities that receive invoices. Same methods as sellers:
client.buyers.create(params) // Register a buyer
client.buyers.get(id) // Get buyer by ID
client.buyers.list(params?) // List buyers (paginated)
client.buyers.update(id, params) // Update buyer details
client.buyers.delete(id) // Delete buyer
client.buyers.bulkDelete(ids) // Bulk delete
client.buyers.verifyTin(id) // Initiate TIN verification
client.buyers.getVerificationStatus(id) // Check TIN verification progress
client.buyers.search(query) // Search by name or TINCreateBuyerParams — Same shape as CreateSellerParams except taxNumber is optional (buyers may not have a TIN).
Billing — client.billing
Manage your organization's billing account, subscriptions, credits, and payment methods.
// Account & Balance
client.billing.getAccount() // Credit balance, status, billing mode
client.billing.checkBalance(accountId, credits) // Check if account has enough credits
client.billing.getAccountStats(accountId) // Account statistics (lifetime totals, averages)
// Plans & Packages
client.billing.getPlans(params?) // Available subscription plans
client.billing.getPlan(code) // Get plan by code (e.g. 'starter', 'pro')
client.billing.getPackages(params?) // Available one-time credit packages
client.billing.getPackage(id) // Get package by ID
client.billing.getAllPlans(params?) // All plans (subscriptions + packages combined)
// Subscriptions
client.billing.getActiveSubscription() // Current active subscription
client.billing.getSubscriptionHistory() // Past subscriptions
client.billing.getSubscription(id) // Get subscription by ID
client.billing.createSubscription(params) // Subscribe to a plan
client.billing.subscribeAndPay(params) // Subscribe and initiate payment in one step (returns checkout URL)
client.billing.cancelSubscription(id, params?) // Cancel (immediate or at period end)
client.billing.resumeSubscription(id) // Resume a cancelled subscription
client.billing.changePlan(id, params) // Upgrade/downgrade plan
client.billing.getRenewalHistory(params?) // Subscription renewal ledger
client.billing.getRenewalStats() // Renewal statistics (totals, averages)
// Credits & Payments
client.billing.purchaseCredits(params) // Buy a credit package (returns checkout URL)
client.billing.getPayments(params?) // Payment history
client.billing.getTransactions(params?) // Credit transaction history (purchases, consumption, refunds)
client.billing.getTransaction(id) // Get a single transaction by ID
client.billing.transferCredits(params) // Transfer PAYG credits between parent and child orgs
// Payment Methods
client.billing.getPaymentMethods() // List saved methods
client.billing.addPaymentMethod(params) // Add a method (returns setup URL)
client.billing.removePaymentMethod(provider, id) // Remove a method
client.billing.setDefaultPaymentMethod(provider, params) // Set default
// Analytics
client.billing.getUsageAnalytics(params?) // Submission counts, credit usage, daily breakdown
client.billing.getConsumptionBreakdown() // Credit consumption grouped by API endpointOrganizations — client.organizations
Manage your organization's profile, onboarding, and verification. These methods operate on your own organization (the one associated with your API key).
client.organizations.create(params) // Create a sub-organization (B2B2B partners only)
client.organizations.list() // List your organizations
client.organizations.get(id?) // Get org by ID, or your own org if no ID
client.organizations.update(params) // Update your org profile
client.organizations.completeOnboarding(params) // Complete onboarding (businessName, taxNo, email)
client.organizations.verifyTaxNumber({ taxNo }) // Verify your TIN with tax authority
client.organizations.sendPhoneVerification({ phoneNumber }) // Send phone verification OTP
client.organizations.verifyPhone({ otp }) // Verify phone with OTP code
// Organization hierarchy (primary orgs only)
client.organizations.getChildren() // List child (secondary) organizations
client.organizations.getCreditPoolConfig() // Get credit pool configuration
client.organizations.updateCreditPoolConfig(params) // Update credit pool configurationInvitations — client.invitations
Requires organizationId in constructor (except verify and accept which are public).
client.invitations.create(params) // Invite user to organization
client.invitations.list(params?) // List invitations
client.invitations.resend(id) // Resend invitation email
client.invitations.revoke(id) // Revoke an invitation
// Public endpoints (no auth required)
client.invitations.verify(params) // Verify an invitation code is valid
client.invitations.accept(params) // Accept invitation and create accountWebhooks — client.webhooks
Requires organizationId in constructor.
client.webhooks.create(params) // Create webhook endpoint
client.webhooks.list(params?) // List endpoints
client.webhooks.get(id) // Get endpoint by ID
client.webhooks.update(id, params) // Update endpoint (URL, events, etc.)
client.webhooks.delete(id) // Delete endpoint
client.webhooks.test(id) // Send a test event to the endpoint
client.webhooks.listDeliveries(webhookId, params?) // List delivery attempts for an endpoint
client.webhooks.retryDelivery(deliveryId) // Retry a failed deliveryAvailable webhook event types:
| Event | Description |
|---|---|
invoice.created |
Invoice draft created |
invoice.updated |
Invoice updated |
invoice.submitted |
Invoice submitted to tax authority |
invoice.approved |
Invoice accepted by tax authority |
invoice.rejected |
Invoice rejected by tax authority |
invoice.cancelled |
Invoice cancelled (credit note created) |
invoice.status.changed |
Invoice status changed |
invoice.submission_failed |
Invoice submission failed |
invoice.retry_scheduled |
Invoice retry scheduled |
seller.created |
Seller created |
seller.updated |
Seller updated |
seller.tax_number_verified |
Seller TIN verified |
seller.tax_number_verification_failed |
Seller TIN verification failed |
buyer.created |
Buyer created |
buyer.updated |
Buyer updated |
buyer.deleted |
Buyer deleted |
buyer.tax_number_verified |
Buyer TIN verified |
buyer.tax_number_verification_failed |
Buyer TIN verification failed |
user.created |
User created |
user.updated |
User updated |
user.deleted |
User deleted |
user.role_changed |
User role changed |
user.status_changed |
User status changed |
organization.created |
Organization created |
organization.updated |
Organization updated |
organization.status_changed |
Organization status changed |
organization.tax_number_verified |
Organization TIN verified |
organization.onboarding_completed |
Organization onboarding completed |
organization.subscription_tier_changed |
Organization subscription tier changed |
api_key.created |
API key created |
api_key.revoked |
API key revoked |
api_key.rotated |
API key rotated |
api_key.permissions_changed |
API key permissions changed |
payment.created |
Payment created |
payment.succeeded |
Payment completed successfully |
payment.failed |
Payment attempt failed |
billing_account.status_changed |
Billing account status changed |
billing_account.low_balance |
Credit balance below threshold |
subscription.created |
Subscription created |
subscription.renewed |
Subscription renewed |
subscription.cancelled |
Subscription cancelled |
subscription.expired |
Subscription expired |
subscription.plan_changed |
Subscription plan changed |
subscription.payment_failed |
Subscription payment failed |
subscription.payment_failed_final |
Subscription payment failed (final attempt) |
credit.balance_updated |
Credit balance updated |
credit.purchased |
Credits purchased |
webhook.test |
Test webhook event |
* |
Subscribe to all events |
API Keys — client.apiKeys
Requires organizationId in constructor.
client.apiKeys.create(params) // Create API key (returns raw key ONCE — store immediately)
client.apiKeys.list(params?) // List API keys (raw key is never shown again)
client.apiKeys.get(id) // Get API key metadata
client.apiKeys.revoke(id) // Revoke an API key (irreversible)
client.apiKeys.rotate(id) // Rotate key (returns new raw key, old key expires)
client.apiKeys.updateRole(id, params) // Change the role assigned to a key
client.apiKeys.getPermissions(id) // Get the key's effective permissionsImportant:
create()androtate()are the only methods that return the raw API key. Store it securely (encrypted at rest) immediately — it cannot be retrieved again. If lost, userotate()to generate a new one.
Users — client.users
Requires organizationId in constructor. Manages users within the specified organization.
client.users.list(params?) // List users (paginated, filterable)
client.users.get(id) // Get user by ID
client.users.getMe() // Get the current authenticated user
client.users.update(id, params) // Update user profile
client.users.updateRole(id, { roleId }) // Change user's role
client.users.updateStatus(id, { status }) // Activate, suspend, or deactivate user
client.users.getPermissions(id) // Get user's roles + effective permissions
client.users.addPermissions(id, { permissions }) // Add custom permissions
client.users.removePermissions(id, { permissions }) // Remove custom permissions
// Phone Verification
client.users.sendPhoneVerification(id, { phoneNumber }) // Send OTP to verify phone
client.users.verifyPhone(id, { phoneNumber, otp }) // Verify phone with 6-digit OTPRoles — client.roles
Requires organizationId in constructor. Manage custom RBAC roles for your organization.
client.roles.list() // List all roles (system + custom)
client.roles.create(params) // Create a custom role with specific permissions
client.roles.get(roleId) // Get role details with assignment counts
client.roles.update(roleId, params) // Update a custom role (system roles are immutable)
client.roles.delete(roleId) // Delete a custom role (removes all assignments)
client.roles.getAvailablePermissions() // Get available permissions catalog by categoryCreateRoleParams:
{
name: string; // Internal identifier (e.g. 'invoice_manager')
displayName: string; // Display name (e.g. 'Invoice Manager')
description?: string; // Optional description
permissions: string[]; // Array of permission strings (e.g. ['invoice.create', 'invoice.read'])
}B2B2B Partner Guide
If you're a platform (like an ERP, POS, or commerce system) that onboards multiple businesses onto E-Invoice, this section is for you.
How It Works
Your platform is a partner organization on E-Invoice. Each business you onboard becomes a child organization with its own isolated data, API key, and billing account.
┌─────────────────────────────────────────┐
│ Your Platform (Partner) │
│ API Key: sk_live_partner_... │
│ │
│ Can: create child orgs, manage their │
│ users and API keys │
│ │
│ Cannot: access child org invoices, │
│ sellers, buyers, or billing │
├─────────┬─────────┬─────────┬──────────┤
│ Child A │ Child B │ Child C │ ... │
│ Own key │ Own key │ Own key │ │
│ Own data│ Own data│ Own data│ │
└─────────┴─────────┴─────────┴──────────┘Step 1 — Initialize your partner client
const partner = new EInvoice({
apiKey: 'sk_live_partner_key',
organizationId: 'your_partner_org_id', // Your own org ID
});Step 2 — Onboard a child organization
const { organization, apiKey } = await partner.createOrganizationWithApiKey(
{ businessName: 'Acme Nigeria', email: 'admin@acme.ng' },
{ mode: 'production', name: 'acme-primary-key' },
);
// IMPORTANT: apiKey.key is the raw key — shown ONCE.
// Store it encrypted (e.g. AES-256-GCM) in your database immediately.
// If lost, use apiKeys.rotate() on a partner-scoped client to generate a new one.
console.log(apiKey.key); // 'sk_live_child_...'
console.log(organization.id); // 'org_child_123'Step 3 — Operate as the child org (invoices, sellers, buyers, billing)
Use forApiKey() with the child org's own API key. This creates a new client that inherits your config (timeout, retry, baseUrl) but authenticates as the child org.
const acme = partner.forApiKey(apiKey.key, organization.id);
// Now all operations are scoped to Acme's data
const seller = await acme.sellers.create({ ... });
const buyer = await acme.buyers.create({ ... });
const invoice = await acme.invoices.create({ ... });
await acme.invoices.submit(invoice.data.id);
await acme.billing.getAccount();Step 4 — Manage child org resources with your partner key
Use forOrganization() with the child org's ID. This creates a client using your partner key but scoped to the child org's ID in the URL path.
const acmeAdmin = partner.forOrganization(organization.id);
await acmeAdmin.apiKeys.list(); // List Acme's API keys
await acmeAdmin.users.list(); // List Acme's users
await acmeAdmin.webhooks.create({ ... }); // Create webhook for AcmeWarning:
forOrganization()uses your partner's API key, NOT the child org's key. This only works for endpoints where the partner has cross-org authority: API keys, users, and webhooks. It does NOT work for invoices, sellers, buyers, or billing — those require the child org's own key viaforApiKey().
When to use which method
| Method | API Key Used | Org Scope | Use For |
|---|---|---|---|
new EInvoice({ apiKey, organizationId }) |
Your own | Your own org | Partner self-management |
partner.forOrganization(childOrgId) |
Your partner key | Child org (URL path) | Managing child's API keys, users, webhooks |
partner.forApiKey(childKey, childOrgId) |
Child org's key | Child org | Invoices, sellers, buyers, billing for the child |
partner.createOrganizationWithApiKey(...) |
Your partner key | Creates new child | One-time child org onboarding |
Credit Pool Configuration
Primary organizations control how credits flow to their child (secondary) orgs via creditPoolConfig:
// Get current config
const config = await partner.organizations.getCreditPoolConfig();
// Update config
await partner.organizations.updateCreditPoolConfig({
primaryCoversSecondary: true,
minimumReserve: 500,
});| Config | Behavior |
|---|---|
primaryCoversSecondary: true |
Primary's pool is debited for all child org usage. Child's balance is unchanged. |
primaryCoversSecondary: false, fallbackToPrimary: false |
Child uses its own pool only. Blocked when empty. |
primaryCoversSecondary: false, fallbackToPrimary: true |
Child's pool first, then primary covers the shortfall (respecting minimumReserve). |
Credit Transfers
Transfer PAYG credits between parent and child organizations:
// Primary → child: fund the child org
await partner.billing.transferCredits({
targetOrganizationId: organization.id,
amount: 1000,
description: 'Monthly credit allocation',
});
// List child orgs and their balances
const children = await partner.organizations.getChildren();Transfers use the paygCredits pool only. Both orgs must be in payg or hybrid billing mode. Primary-to-child transfers respect the minimumReserve setting.
Error Recovery
If createOrganizationWithApiKey() succeeds in creating the org but fails when creating the API key (e.g. network error), the org will exist without a key. Recover by creating the key manually:
const scoped = partner.forOrganization(organization.id);
const newKey = await scoped.apiKeys.create({ mode: 'production', name: 'retry-key' });Webhook Verification
Verify incoming webhook signatures using the standalone verifyWebhook function:
import { verifyWebhook } from '@elyonar/einvoice-js/webhooks';
// Pass the RAW request body (string or Buffer)
const event = verifyWebhook(rawBody, requestHeaders, process.env.WEBHOOK_SECRET!);
console.log(event.type); // 'invoice.approved'
console.log(event.id); // 'evt_abc123'
console.log(event.environment); // 'sandbox' | 'production'
console.log(event.data); // Event payloadThe function parses the body, extracts the data field, and verifies the HMAC-SHA256 signature against it (timing-safe comparison). Rejects payloads older than 5 minutes (replay protection). Throws EInvoiceWebhookError on failure.
Important: Pass the raw request body as a string or Buffer — not
JSON.parse()'d.
// Custom timestamp tolerance (default: 300 seconds)
const event = verifyWebhook(body, headers, secret, { toleranceSeconds: 600 });Error Handling
The SDK uses a structured error hierarchy. All errors extend EInvoiceError.
import {
EInvoiceApiError,
EInvoiceRateLimitError,
EInvoiceTimeoutError,
EInvoiceConfigError,
EInvoiceWebhookError,
} from '@elyonar/einvoice-js';
try {
await client.invoices.submit('inv_123');
} catch (err) {
if (err instanceof EInvoiceRateLimitError) {
// HTTP 429 — rate limit exceeded after all retries
console.log(`Rate limited. Retry after ${err.retryAfter} seconds`);
} else if (err instanceof EInvoiceApiError) {
// HTTP 4xx/5xx from the API
console.log(err.statusCode); // 422
console.log(err.errorCode); // 'VAL001'
console.log(err.message); // 'Validation failed'
// Field-level errors — useful for form validation
console.log(err.errors);
// [
// { field: 'lineItems', message: 'At least one line item is required' },
// { field: 'invoiceDate', message: 'Invoice date cannot be in the future' },
// ]
} else if (err instanceof EInvoiceTimeoutError) {
// Request took longer than the configured timeout
console.log(`Timed out after ${err.timeoutMs}ms`);
} else if (err instanceof EInvoiceConfigError) {
// SDK misconfiguration (missing apiKey, missing organizationId, etc.)
console.log(err.message);
} else if (err instanceof EInvoiceWebhookError) {
// Webhook signature verification failed
console.log(err.message); // 'Webhook signature verification failed'
}
}Error Hierarchy
EInvoiceError (base)
├── EInvoiceApiError — API returned 4xx/5xx
│ └── EInvoiceRateLimitError — API returned 429 (has retryAfter)
├── EInvoiceTimeoutError — Request exceeded timeout (has timeoutMs)
├── EInvoiceConfigError — Invalid SDK configuration
└── EInvoiceWebhookError — Webhook signature verification failedAutomatic Retries
The SDK automatically retries on:
- 5xx errors (server errors) — up to
maxAttemptswith exponential backoff - 429 errors (rate limiting) — waits for
Retry-Afterheader, then retries
The SDK does NOT retry:
- 4xx errors (client errors like 400, 401, 403, 404, 422) — these indicate a problem with your request
Per-Request Options
Every method accepts an optional RequestOptions parameter as the last argument:
const controller = new AbortController();
const invoices = await client.invoices.list(
{ status: 'draft', page: 1 },
{
timeout: 5000, // Override timeout for this request only
signal: controller.signal, // AbortSignal for cancellation
headers: { 'X-Request-Id': 'req_123' }, // Additional headers for this request
},
);
// Cancel the request
controller.abort();Development
npm install # Install dependencies
npm test # Run tests with coverage
npm run build # Build ESM + CJS
npm run lint # Type check
npm run dev # Watch mode (ESM only)License
MIT