Skip to content
ELECT-IGLOBAL
Merchant API

Create a paid order from your own storefront

A small, signed REST API for Merchant and Elite accounts: create prepaid orders on a customer's behalf, read them back, cancel the unclaimed ones, and check your wallet.

Who can call it

API access follows the membership tier of the account that owns the key. Merchant keys are read-only. Elite keys can write. A key on a Buyer account is rejected with tier_forbidden.

Base URL

Every endpoint below is relative to this base, and every request must be HTTPS.

https://elect-i-global.com

01

Authentication

Every request is signed. There is no bearer token: a captured header is useless without the secret, and a captured request is useless after five minutes.

HeaderWhat it carries
X-EIG-Key-IdThe public key identifier issued with the key pair.
X-EIG-TimestampUnix time in seconds. A request more than 300 seconds from server time is rejected.
X-EIG-NonceA value you have never sent before on this key. It is stored server-side, so a repeat is rejected.
X-EIG-SignatureLowercase hex HMAC-SHA256 of the canonical string below, keyed on your own secret.

The canonical string

Five fields joined by a single newline character. The path excludes the query string. The body is the exact bytes you send — for a GET request that is the empty string.

canonical
<timestamp>
POST
/api/v1/orders
<nonce>
<raw request body>

Sign the body you actually transmit. Re-serialising the JSON after signing changes the bytes and breaks the signature.

Signing a request

This example uses WebCrypto, so it runs unchanged on Node 18+, Deno, Bun and Cloudflare Workers.

sign.ts
const canonical = [timestamp, "POST", path, nonce, body].join("\n");
const key = await crypto.subtle.importKey(
  "raw", new TextEncoder().encode(secret),
  { name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(canonical));
const signature = [...new Uint8Array(sig)]
  .map((b) => b.toString(16).padStart(2, "0")).join("");

The same request with curl

request.sh
curl -X POST https://elect-i-global.com/api/v1/orders \
  -H "X-EIG-Key-Id: $KEY_ID" \
  -H "X-EIG-Timestamp: $TS" \
  -H "X-EIG-Nonce: $(uuidgen)" \
  -H "X-EIG-Signature: $SIG" \
  -H "content-type: application/json" \
  --data "$BODY"

02

Endpoints

Five endpoints. Everything else on the platform is deliberately not exposed.

POST/api/v1/ordersorders:writeElite

Charges the wallet or credit line named in pay_from, then returns a claim link for the recipient. Catalog lines are repriced on the server from the SKU, so a unit price sent for a line that resolves to a catalog product is ignored. Send the same idempotency_key twice and the original order comes back untouched.

GET/api/v1/ordersorders:readMerchant+

Your orders, newest first. Accepts limit (default 25, maximum 100) and status.

GET/api/v1/orders/:idorders:readMerchant+

Full detail including line items and the claim link. Recipient data is limited to name, country and city, and only after the recipient has confirmed their details: you paid for the order, but you do not need the buyer's full address to reconcile it.

POST/api/v1/orders/:id/cancelorders:writeElite

Cancels an order the recipient has not yet claimed and returns the money to the source it came from. Once a recipient has confirmed their delivery details the order can no longer be cancelled through the API.

GET/api/v1/walletwallet:readMerchant+

Balance, credit headroom, your tier's monthly prepaid-order allowance and the last twenty ledger entries.

Request

POST /api/v1/orders
POST /api/v1/orders
X-EIG-Key-Id: eig_live_1a2b3c4d
X-EIG-Timestamp: 1769000000
X-EIG-Nonce: 6f9c1e07-...-b2
X-EIG-Signature: 4d5e...c1

{
  "idempotency_key": "order-9f2c",
  "external_ref": "I20260713175900",
  "on_behalf": true,
  "pay_from": "wallet",
  "items": [{ "sku": "EIG-4A7C21B9", "qty": 24 }],
  "external_items": [
    { "name": "USB-C 65W charger", "sku": "EIG-4A7C21B9",
      "qty": 24, "unit_price_cents": 1720 }
  ],
  "external_digest": "9b1f...e4"
}

Response

201 Created
201 Created

{
  "order_id": "b6f1...",
  "order_number": "EIG-20260722-0F3A1",
  "status": "awaiting_recipient",
  "claim_url": "https://elect-i-global.com/en/claim/xY...",
  "amount_charged": "412.80",
  "currency": "USD",
  "wallet_balance_after": "8190.40",
  "items_digest": "9b1f...e4",
  "digest_matches": true
}

If you send external_items together with the external_digest you computed over them, the response reports whether your digest matches the one ELECT-I computed independently. That comparison is what makes the recipient's claim page meaningful, so compute the digest on your side rather than copying ours.

03

Webhooks

Elite accounts can register HTTPS endpoints and receive order lifecycle events. Deliveries are queued first and attempted afterwards, so an endpoint that is down loses nothing.

order.createdA prepaid order was created and a claim link issued.order.claimedThe recipient verified the order and confirmed delivery details.order.shippedThe order left the warehouse; tracking is attached.order.deliveredThe carrier recorded delivery.order.cancelledThe order was cancelled; the payload says whether it was refunded.wallet.low_balanceThe wallet balance fell below the low-balance threshold after a charge.

Verifying a delivery

Each POST carries x-eig-event, x-eig-timestamp and x-eig-signature. The signature is a lowercase hex HMAC-SHA256 over the string <timestamp>.<raw body>, keyed on that endpoint's own signing secret. Compare it in constant time and reject anything that does not match.

Retries

A delivery is attempted up to six times with growing gaps of roughly 30 seconds, 2 minutes, 8 minutes, 30 minutes and 2 hours. Any 2xx response ends the sequence. Endpoints must be idempotent, because a delivery you have already processed can arrive again.

04

Errors

Every failure returns the same envelope: an error object with a stable code and a human-readable message. Match on the code, never on the message.

CodeStatusMeaning
missing_headers401One of the four signing headers was absent.
unknown_key401No such key, or the stored secret failed its own integrity cross-check.
key_revoked401The key exists but has been revoked in the dashboard.
stale_timestamp401The timestamp is more than 300 seconds from server time.
replayed_nonce409That nonce has already been used on this key.
bad_signature401The signature did not match the canonical string.
account_suspended403The account behind the key is suspended.
tier_forbidden403The tier has no API access, or the membership is not active.
insufficient_funds402The wallet balance does not cover the order total.
credit_limit402The order would exceed the agreed credit line.
quota_exceeded429The tier's monthly prepaid-order allowance is used up.
moq_not_met422A line is below the minimum order quantity that applies at your tier.
unknown_product422A SKU or product id does not resolve to an available listing.
not_found404No such order for this account. Orders belonging to another merchant return the same 404 rather than confirming that they exist.
already_claimed409The recipient has already confirmed delivery details, so the order cannot be cancelled.
invalid_state409The order's current status does not allow that transition.
server_error500Something failed on our side. Retry with the same idempotency key.

Ready to build?

Create a key in the dashboard, or ask us about the integration first.