Tassyir ERP
@akadli/provider-tassyir connects Tassyir (api.tassyir.io), the leading Cash-on-Delivery (COD) and courier management ERP in Algeria, with the Akadli-AI voice confirmation engine.
It implements the official Tassyir Admin API (2026-10) specification, zero-secret Standard Webhooks (v1a Ed25519) verification, install-code OAuth flows with HTTP Basic Auth, and idempotent order confirmations.
Installation
pnpm add @akadli/provider-tassyirTassyir App Setup (tassyir.app.toml)
When creating an external headless application on the Tassyir Developer platform (developers.tassyir.io), define your tassyir.app.toml:
name = "Akadli"handle = "akadli"api_version = "2026-10"
[access]scopes = ["read_orders", "write_orders", "read_customer_contact", "read_products"]
[access.scope_reasons]read_orders = "Read incoming COD orders for voice confirmation"write_orders = "Confirm, modify, or cancel COD orders after voice call"read_customer_contact = "Read buyer phone and address for confirmation call"read_products = "Allows the adapter to fetch alternative sizes/variants"
[auth]callback_url = "https://your-domain.com/api/webhooks/tassyir/callback"
[[webhooks]]topics = ["orders/created"]uri = "/api/webhooks/tassyir"
[hosting]mode = "external"url = "https://your-domain.com"Required OAuth Scopes
read_orders: Ingest incoming orders, total prices, and line items.write_orders: Update order notes, confirm (/confirm), and cancel (/cancel).read_customer_contact: Mandatory for voice confirmation. Without this scope (or if past merchant plan quota), Tassyir masks phone numbers as"**********"and flagswaiting: true. The adapter guards against masked contacts and throws an actionable error.read_products: Recommended. Allows the adapter to fetch alternative sizes/variants fromGET /products/{productId}so voice callers can switch sizes.
Standard Webhooks (v1a Ed25519 Zero-Secret Verification)
Tassyir signs inbound webhooks using the Standard Webhooks (v1a Ed25519) specification. Tassyir does not issue a per-store shared webhook secret.
- Inbound headers:
webhook-id: Unique message ID (e.g.msg_741...).webhook-timestamp: Unix timestamp in seconds. Replay tolerance is 5 minutes (300seconds).webhook-signature: Formatv1a,<base64_ed25519_signature>.
- Public keys (
whpk_<base64_raw_32_byte_ed25519_key>) are fetched dynamically fromGET https://api.tassyir.io/apps/webhook-keys(cached for 1 hour). - The adapter natively decodes raw 32-byte Ed25519 keys using the 12-byte ASN.1 SPKI DER prefix (
302a300506032b6570032100in hex) via Node’snode:crypto. - Embedded fallback key
whpk_1BaNZhSk1qxT/1RugRT9eucQWvfo1ufg0o1u5qZejAA=ensures zero-downtime resilience.
OAuth Installation Code Flow (tsy_ic_...)
Tassyir apps authenticate using OAuth 2.0:
- When a merchant installs your app, Tassyir redirects their browser to your
callback_urlwith an install code:GET /api/webhooks/tassyir/callback?code=tsy_ic_... - Your backend redeems
tsy_ic_...viaPOST https://api.tassyir.io/apps/oauth/token:- Header:
Authorization: Basic base64(client_id:client_secret) - Header:
Content-Type: application/x-www-form-urlencoded - Body:
grant_type=authorization_code&code=tsy_ic_... - (Note:
client_secretmust NOT be in the body). - Response:
{"access_token": "tsy_at_...","refresh_token": "tsy_rt_...","expires_in": 3600,"token_type": "Bearer"}
- Header:
CLI & Dev Tools
Tassyir provides an official CLI for scaffolding and testing webhooks:
# Verify app configuration or test webhooks locallynpx tassyir@latest app devCredentials Schema
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
accessToken | string | Optional* | — | Direct Bearer token (tsy_at_...) |
clientId | string | Optional* | — | App Client ID (app_...) |
clientSecret | string | Optional* | — | App Client Secret (tsy_cs_...) |
refreshToken | string | Optional | — | Refresh token (tsy_rt_...) for automatic renewal |
baseUrl | string | No | "https://api.tassyir.io/apps/2026-10" | Admin API base endpoint |
confirmOnModified | string | No | "true" | Calls /confirm when size/address is updated |
cancelOnRefused | string | No | "true" | Calls /cancel when order is refused |
Code Example
import { TassyirProvider } from "@akadli/provider-tassyir";
const provider = new TassyirProvider();
// 1. Zero-secret Ed25519 signature verification against published whpk_ keysconst isValid = await provider.verifyWebhook(headers, rawBody);
if (!isValid) { throw new Error("Invalid Tassyir Ed25519 signature");}
// 2. Parse 2026-10 order envelope and resolve 69-Wilaya divisionconst order = await provider.parseWebhookOrder(JSON.parse(rawBody));
// 3. Mutate Tassyir order with idempotent confirmationconst result = await provider.applyOrderDecision( order.orderId, { orderId: order.orderId, status: "CONFIRMED", reasonOrNotes: "Confirmed in Darja via voice call" }, { clientId: process.env.TASSYIR_CLIENT_ID!, clientSecret: process.env.TASSYIR_CLIENT_SECRET!, refreshToken: process.env.TASSYIR_REFRESH_TOKEN! });