العودة إلى الوثائق

واجهة برمجة تطبيقات الفاتورة الإلكترونية لهيئة الإمارات للضرائب — دليل تكامل شريك نظام تخطيط موارد المؤسسات

قم بتوصيل نظام تخطيط موارد المؤسسات الخاص بك بـ TrustBill للامتثال للفاتورة الإلكترونية لهيئة الإمارات للضرائب · واجهة برمجة التطبيقات · خطافات الويب · v1.5

نظرة عامة

ثلاثة بيانات اعتماد، ثلاثة مالكين

  • API Key + API Secretone set per ERP install, مفتاح API + سر API — مجموعة واحدة لكل تثبيت ERP، يحتفظ بها شركة الاستشارات أو بائع ERP. جميع الشركات الصغيرة والمتوسطة على هذا ERP تشاركها.
  • Webhook Secretone per ERP install. سر خطاف الويب — واحد لكل تثبيت ERP. يرسله TrustBill كـ x-webhook-secret على كل خطاف حالة ويب؛ يتحقق منه ERP الخاص بك.
  • Connection Secret (tb_secret_…)one per SME. سر الاتصال (tb_secret_…) — واحد لكل شركة صغيرة ومتوسطة. تنقر كل شركة صغيرة ومتوسطة على Connect في بوابة TrustBill الخاصة بها، تحصل على سر فريد، تلصقه في إعدادات شركتها داخل ERP الخاص بك. يتم إرساله كـ x-erp-partner-secret على كل دفعة.

إذا قمت بتصميم سر "لكل مستلم فاتورة" أو "لكل عميل"، فأنت تقوم بذلك بشكل خاطئ — يحدد سر الاتصال البائع (الشركة الصغيرة والمتوسطة التي تصدر الفاتورة)، وليس المشتري أبدًا.

يصف هذا الدليل كيفية دمج نظام ERP الخاص بك (Odoo، NetSuite، SAP، مخصص، إلخ) مع TrustBill بحيث يمكن لعملاء الشركات الصغيرة والمتوسطة لديك إرسال فواتير ضريبة الفاتورة الإلكترونية لهيئة الإمارات للضرائب مباشرة من ERP الخاص بك — دون مغادرة سير العمل الحالي.

التكامل ثنائي الاتجاه. يدفع ERP الخاص بك الفواتير إلى TrustBill عبر HTTP POST. يرسلها TrustBill إلى هيئة الإمارات للضرائب/مزود الخدمة ويتصل مرة أخرى بخطاف الويب الخاص بك عندما تتغير الحالة (تم التسليم أو تم الرفض).

من يفعل ماذا

  • Consultancy (you): شركة الاستشارات (أنت): تسجل تكامل ERP مرة واحدة، توفر عنوان URL لخطاف الويب + سر، تحصل على بيانات اعتماد API للاختبار، تطلب الموافقة المباشرة.
  • SME (your client): الشركة الصغيرة والمتوسطة (عميلك): تنقر على "Connect" في صفحة مصادر TrustBill الخاصة بهم، تحصل على سر اتصال لكل شركة صغيرة ومتوسطة للصق في ERP الخاص بك.
  • Your ERP: ERP الخاص بك: يدفع كل فاتورة إلى POST /v1/partner/invoices مع بيانات اعتماد API + سر اتصال لكل شركة صغيرة ومتوسطة.
  • TrustBill: TrustBill: يصادق على الدفعة، يرسل إلى هيئة الإمارات للضرائب، ثم يرسل تحديثات الحالة إلى عنوان URL لخطاف الويب الخاص بك.

The four-step flow

  Step 1 (once, per consultancy)
  ─────────────────────────────
  Consultancy portal  ──(register ERP + webhook URL + secret)──►  TrustBill
                                     │
                                     └── admin approval → status: live_active

  Step 2 (once, per SME)
  ──────────────────────
  SME TrustBill portal ──(click "Connect")──►  TrustBill
                                     │
                                     └── returns tb_secret_<64hex> (shown ONCE)
                                     │
                                     ▼
                            SME pastes into your ERP settings

  Step 3 (per invoice)
  ────────────────────
  Your ERP ──POST /v1/partner/invoices──►  TrustBill ──►  FTA/ASP
                headers:
                  X-API-Key: erp_live_...
                  X-API-Secret: ...
                  x-erp-partner-secret: tb_secret_...
                  Idempotency-Key: <unique>
                → 201 { invoiceId, number, status: "queued" }

  Step 4 (async, after FTA responds)
  ──────────────────────────────────
  TrustBill ──POST <your_webhook_url>──►  Your ERP
                header:
                  x-webhook-secret: <the secret you registered in Step 1>
                body:
                  { event, erpPartnerId, invoiceId, status, reason, timestamp }

الميزات — مطلوبة، موصى بها، اختيارية

ليس كل ميزة يجب أن تُشحن في اليوم الأول. تخبر هذه المصفوفة فريق الهندسة الخاص بك كيف يبدو التكامل الحد الأدى القابل للحياة (شحن هذا وستكون متوافقًا مع هيئة الإمارات للضرائب)، وما يجعله جاهزًا للإنتاج، وما هو التلميع الاختياري. كما تسرد القدرة التي تفتحها كل ميزة بحيث تعرف ما تتنازل عنه إذا تخطيتها.

Requiredمطلوب — حاجز اليوم الأول، التكامل لا يعمل بدونهRecommendedموصى به — مطلوب للبقاء على قيد الحياة في حوادث الإنتاجOptionalاختياري — تلميع أو حالة استخدام متقدمة

مطلوب — التكامل الحد الأدنى القابل للحياة

FeatureWhat it doesUnlocks
Credential storage — API Key + Secret, per-SME connection secret, webhook secret4 encrypted-at-rest strings. API Key/Secret + Webhook Secret are one-per-ERP; connection secret is one-per-SME.Auth for every single call.
Submit invoicePOST /v1/partner/invoicesMap local invoice → TrustBill body; send with API creds + connection secret + a unique Idempotency-Key; persist the returned invoiceId.Push invoice to the FTA. Nothing else in the integration matters if this doesn't work.
Status webhook receiver — public HTTPS endpointAccept POST from TrustBill, verify auth (see next row), update local invoice status + reason, return 2xx within 8 s.Real-time status without polling. Without it, invoices stay Queued in your UI forever.
Webhook auth — one of: HMAC (v1.5+) OR plain x-webhook-secretVerify the caller is genuinely TrustBill. Constant-time compare or HMAC-SHA256; reject with 401 otherwise.Security. Without this an attacker who guesses your URL can forge status updates into your ERP.
Local invoice status field — enumStore queued / delivered / rejected on the local invoice row. Populate at Step 3 (queued) and Step 4 (delivered/rejected).Accounting can see what's FTA-compliant. Filters & reports depend on this.
SME's own TRN — settings input15-digit field in company/SME setup. This is what goes in top-level body.trn — must exactly match the TRN registered on trustbill.ae for the same company.Passes TrustBill's TRN-vs-SME cross-check. Any mismatch → 403.
Submit button on invoice detailManual trigger to push. Disable when local validation fails.Operator control — some invoices need review before FTA.
Idempotency-Key generationStable per (invoice, attempt). Pattern: <erp-tenant-id>-<local-invoice-id>-<attempt>. Store the key you sent so a retry uses the same one.Safe retries — TrustBill de-dupes; you never create duplicate invoices on network flakes.

موصى به — مطلوب للبقاء على قيد الحياة في الإنتاج

FeatureWhat it doesUnlocks
Polling fallbackGET /v1/partner/invoices/{id} (v1.5)Scheduled job every ~15 min that fetches status for anything stuck in queued for > 5 min. Also a manual "Sync now" button.Survival when webhooks are lost — TrustBill fires once, no retry. Without polling, one dropped webhook = permanently stuck invoice.
HMAC signature verify (v1.5)Verify x-webhook-signature header (HMAC-SHA256 over timestamp + body) with a 5-min replay window. Fall back to plain secret only when HMAC absent.Replay-safe production security. Auditors will ask.
Test connection buttonGET /v1/partner/whoamiOne-click check from Settings that shows mode (test/live) + connected SME count + partner name.Saves 30-min "why doesn't it work?" debug sessions per install.
Retry-on-rejection buttonOnly visible when status = Rejected. Generates a NEW Idempotency-Key and pushes again.Recovery path when a rejection was due to fixable data (wrong TRN, missing address). Without it, operator has to duplicate the invoice.
Rejection reason panelShow the reason string from the webhook on the invoice detail screen so the SME can fix + retry without contacting support.Faster time-to-fix; reduces support load.
Structured error handlingSwitch on TrustBill's code field in 4xx responses: invalid_body → show validation errors; trn_mismatch → tell SME to fix TRN; sme_not_connected → tell them to reconnect on trustbill.ae.Human-friendly errors instead of "500 Server Error".
Test vs Live mode isolationDetect key prefix (erp_test_ vs erp_live_) and surface which mode is active in the ERP UI. Ideally allow separate test-mode credentials in a QA install.Safe QA. Prevents "we thought we were on test but pushed to production" incidents.
Webhook audit logPersist every inbound webhook (raw body, headers, signature-ok flag, timestamp). 90-day retention minimum.Support triage. When someone reports "invoice X wasn't marked delivered", you can prove whether TrustBill ever called.
Invoice list status filterFilter chip: TrustBill status Queued / Delivered / Rejected.Accounting can find everything that needs attention in one click.
Dashboard KPI widgetN queued · N delivered today · N rejected today · N stuck > 15 min.Operator noticies problems before customers do.

اختياري — تلميع وحالات استخدام متقدمة

FeatureWhat it doesUnlocks
Document-type pickerdocumentType field (v1.5)Dropdown on invoice detail: Tax Invoice / Simplified Tax Invoice / Credit Note / Debit Note / Commercial Invoice / Self-Billed. Sent as documentType in the body.Full UBL taxonomy. Without this you can only push standard tax_invoice (388) — fine for most B2B, insufficient for refunds/simplified/self-billed.
Credit / debit note supportprecedingInvoiceReferenceWhen user creates a credit or debit note, capture which original invoice it amends, send as precedingInvoiceReference. Required by FTA for amendments.Legally-valid refunds and adjustments. Without it, the FTA rejects amendments.
Simplified tax invoice — B2C supportFor sales < AED 10,000 to non-VAT-registered buyers. Set documentType: "simplified_tax_invoice". Buyer TRN not required.B2C sales. Skip if your ERP only serves B2B.
Self-billed invoicedocumentType: "self_billed_invoice" — buyer generates the invoice on the supplier's behalf. Rare but required in some industries (utilities, agriculture).Industry-specific. Skip unless you serve utility/telecom/agri customers.
Bulk-submit actionAccounting-staff shortcut: multi-select 50 invoices → "Push all to TrustBill" → generates a job that submits with rate-limiting.Month-end catch-up. Otherwise operators click Submit 50 times.
Auto-submit on invoice postToggle in settings: "Push to TrustBill immediately when invoice is posted." Off by default — some SMEs want manual review.Zero-touch compliance for high-volume SMEs.
Analytics dashboardDeeper view: 7/30/90-day delivery rate, top rejection reasons, avg time-to-delivered per SME.Insight for consultancies managing many SMEs.
Multi-language settings labelsArabic + English side-by-side on the TrustBill Integration settings screen (most UAE ERPs already do this).Local-market fit. Optional if your ERP is English-only.
Slack/Teams webhook for rejectionsPost to a channel when invoice status flips to rejected, so ops sees it without checking the dashboard.Proactive incident response.
Reserved-TRN test suite (v1.5)Automated tests using TRN 100000000000000 (always accepts) and 100000000000666 (always rejects) so CI can verify both paths without real SME data.Regression-proof deploys. Highly recommended for any partner shipping updates weekly.

مصفوفة القدرات — نظرة سريعة

Business capabilityFeatures requiredEffort
Push a B2B tax invoice to FTA
(the 80% use case)
All Required rows.~1 week for a Laravel/Node ERP dev.
Handle rejections gracefullyAll Required + rejection panel + retry button + structured error switch.+2 days.
Survive an incident (webhook down, network flake)Above + polling fallback + webhook audit log + dashboard KPI.+2 days.
Serve B2C customersAbove + document-type picker + simplified-invoice type.+1 day.
Handle refunds & adjustmentsAbove + credit/debit note support + precedingInvoiceReference capture.+1 day.
Month-end mass-catch-upAbove + bulk-submit action + rate-limiter.+1 day.
Production-audit-readyEverything Recommended + HMAC verification + reserved-TRN CI tests.+2 days.
Recommended MVP roadmap: خارطة طريق MVP الموصى بها: شحن جميع الصفوف المطلوبة في الأسبوع 1 → شحن جميع الصفوف الموصى بها في الأسبوع 2 → انتقاء الصفوف الاختيارية في الأسابيع 3-4 بناءً على حالات الاستخدام الفعلية لشركاتك الصغيرة والمتوسطة. إجمالي الوقت للإنتاج لفريق ERP متوسط الحجم: ~3 أسابيع.

ما يجب أن يبنيه فريق نظام تخطيط موارد المؤسسات الخاص بك

يوفر TrustBill واجهة برمجة التطبيقات + البوابة. يملك فريق ERP الخاص بك كل شيء على جانب ERP لإكمال المزامنة ثنائية الاتجاه. إليك قائمة التحقق — الشاشات والأزراب وعمال الخلفية وتغييرات نموذج البيانات — مجمعة حسب الاتجاه الذي تخدمه.

1. الشاشات والأزراب (داخل ERP الخاص بك)

ScreenElementWhat it does
Settings → TrustBill Integration (system-wide, one per ERP tenant)Input: TrustBill API KeyPaste the erp_test_... / erp_live_... from Step 1. Store in ERP config.
Same screenInput: TrustBill API Secret (password type)Paste the one-time secret from Step 1. Store encrypted at rest.
Same screenButton: Test connectionCalls GET https://trustbill.ae/api/v1/partner/ping with the credentials, shows OK/failed.
Company/Customer/Client setup (per SME the ERP serves)Input: TrustBill Connection Secret (password type)Paste the tb_secret_... the SME copied from their TrustBill portal in Step 2. Store encrypted, per-SME.
Invoice detail screenButton: Submit to FTATriggers the outbound push (Step 3). Disable until the invoice validates against your existing rules.
Invoice detail screenStatus pill: Queued / Delivered / RejectedReflects the TrustBill state. Queued right after Step 3's 201; updated by the webhook (Step 4).
Invoice detail screen (when rejected)Rejection reason panelShow the reason string from the webhook so the SME can fix and retry.
Invoice detail screenButton: Retry submissionOnly visible when status is Rejected. Generates a NEW Idempotency-Key and pushes again.
Invoice list screenFilter chip: TrustBill statusLets accounting staff surface everything queued/rejected quickly.

2. الاتجاه 1 — ERP → TrustBill (صادر، إرسال الفاتورة)

عميل HTTP في ERP الخاص بك يطلق عندما تنقر الشركة الصغيرة والمتوسطة على Submit to FTA:

  • Look up the SME's stored TrustBill Connection Secret (from company settings) and your global API Key/Secret.
  • Generate a stable Idempotency-Key — recommended: <erp-tenant-id>-<local-invoice-id>-<attempt>. Same key = same result; a new attempt increments the suffix.
  • Map your local invoice schema to TrustBill's body shape (see Step 3 body example).
  • POST to /v1/partner/invoices. Handle 2xx → save the returned invoiceId against your local invoice, set local status = Queued.
  • Handle 4xx with a clear UI message (see Error responses section). 5xx / network failures: retry with the SAME Idempotency-Key (safe — TrustBill de-dupes).
  • Log every attempt (request id, HTTP status, response body) for support triage.

3. الاتجاه 2 — TrustBill → ERP (وارد، مزامنة الحالة)

نقطة نهاية HTTPS عامة على ERP الخاص بك التي يرسل TrustBill إليها عندما يستجيب هيئة الإمارات للضرائب/مزود الخدمة:

  • Expose an HTTPS endpoint — the URL you registered as testWebhookUrl (Step 1). No auth token; auth is via header.
  • On every call: constant-time compare x-webhook-secret header to the secret you registered. Reject with 401 otherwise.
  • Parse the JSON body → look up your local invoice by TrustBill's invoiceId. If not found, log & 200 (don't reject; another region might replay).
  • Update local status (delivered → your Delivered, rejected → your Rejected) + store reason.
  • Return 2xx quickly (< 8 s). Slow work: enqueue an internal job.
  • Make it idempotent — if TrustBill re-sends the same invoiceId+status, don't double-apply (compare against your last stored status).

4. إضافات نموذج البيانات

TableColumnType / purpose
erp_config (single-row)trustbill_api_keystring — one per ERP install.
erp_configtrustbill_api_secret_encencrypted string — never log the plaintext.
erp_configtrustbill_webhook_secret_encencrypted string — used to verify inbound webhooks.
companies (or your SME/tenant table)trustbill_connection_secret_encencrypted string, per-SME. Store separately from the global API secret.
invoicestrustbill_invoice_idnullable UUID — populated after 201 from Step 3.
invoicestrustbill_statusenum: queued | delivered | rejected.
invoicestrustbill_status_reasonnullable string — populated on rejection.
invoicestrustbill_idempotency_keystring — the key you sent; needed for safe retry.
trustbill_webhook_log (new)received_at, invoice_id, status, raw_bodyaudit trail. Retention: 90 days min.

5. الأمان والعمليات

  • Never log secrets — API secret, connection secret, webhook secret. Scrub them from request/response logs.
  • HTTPS everywhere — TrustBill rejects http:// webhook URLs and won't follow redirects.
  • Constant-time compare for x-webhook-secret (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python).
  • Rate-limit your outbound POSTs to avoid tripping TrustBill's ingest limiter under a mass-send.
  • Metric + alert on: 4xx rate on push, webhook signature mismatches, invoices stuck in queued > 15 min.
  • Rotation — when an SME regenerates their connection secret, invalidate the old one in your ERP and prompt them to re-paste.

6. الأشياء الجيدة

  • "Sync now" button on the invoice that manually re-fetches status via GET /v1/invoices/:id (fallback for missed webhooks).
  • Dashboard widget: N invoices queued, N delivered today, N rejected today.
  • Bulk-submit action for accounting staff to push a filtered list of invoices at once (respect rate limits + generate distinct idempotency keys).
  • Test-mode toggle in ERP settings so QA can use erp_test_... credentials without touching live customer data.

الخطوة 1 — تسجيل نظام تخطيط موارد المؤسسات الخاص بك

سجل ERP الخاص بك كشريك مع TrustBill. هذه عملية لمرة واحدة لكل شركة استشارات. ستحصل على بيانات اعتماد API وتكوين عنوان URL لخطاف الويب الخاص بك.

If you're used to integrating with an Accredited Service Provider (ASP) directly, you'd normally email the ASP, wait days for Sandbox keys, then set up your webhook after. TrustBill inverts that.

تعيين المصطلحات
What ERP teams call itWhat TrustBill calls it
Sandbox API keytestApiKey (starts erp_test_...)
Sandbox API secretrevealedTestSecret
Production API keyliveApiKey (starts erp_live_...)
Production API secretrevealedLiveSecret
Sandbox / staging environment"test mode"
كيف يعمل التدفق فعليًا
  1. You fill in the registration form (business info + your webhook URL + your webhook secret).
  2. The moment you submit, TrustBill mints testApiKey + revealedTestSecret and returns them in the response. No approval wait, no email, no KYC step. You're building in the next 30 seconds.
  3. You build against test creds — same endpoint as live (/v1/partner/invoices).
  4. When outbound + webhook are working end-to-end, click Request Live. A TrustBill admin reviews (only human step in the whole flow) and either approves or rejects with a reason. On approval, liveApiKey + revealedLiveSecret are minted; test creds keep working alongside so nothing breaks mid-transition.
"Can I get Sandbox keys first, before setting up my webhook?"

Yes. Webhook URL and Webhook Secret are optional on the registration form. You can register with them empty, get test creds instantly, build and validate outbound first, then come back to /erp-partner and add the webhook config later (“Webhook & connectivity test” card). Click Test integration to confirm TrustBill can reach your receiver.

Sign in to the consultancy portal and go to /erp-partner. Fill in the register form once. Test-mode credentials are minted instantly — no approval needed to start building.

Who provides the Webhook URL & Secret?

You do — the ERP partner. TrustBill doesn't hand these out. Your ERP owns the receiver endpoint (URL) and picks the shared secret. TrustBill just stores them and sends them back on every status callback so your ERP can verify the call actually came from TrustBill.

Webhook URL — the public HTTPS endpoint your ERP exposes
  • Must be reachable from the public internet (no localhost, no private IPs).
  • Format: https://your-erp.com/<any-path>. Common patterns: /webhooks/trustbill, /api/trustbill/webhook.
  • For the Cloud ERP shipped by TrustBill, this is fixed: https://erp.trustbill.ae/trustbill/webhook — the addon registers that route.
  • For a self-hosted ERP (Odoo, NetSuite, custom), you pick the path and wire it to your inbound webhook handler.
Webhook Secret — a strong random string you invent
  • Generate one now, e.g. openssl rand -hex 32 (256 bits of entropy). Any strong random string works — TrustBill doesn't care about format.
  • Save the same value in two places: paste it into this registration form AND into your ERP's webhook-verification setting (the value your ERP will constant-time compare against the x-webhook-secret header on every inbound call).
  • Store encrypted at rest inside your ERP. Never log the plaintext. Never expose it in client-side code.
  • If it ever leaks, generate a new one and update both sides — TrustBill's Webhook Secret can be rotated any time from the dashboard.
Analogy
Same pattern as GitHub / Stripe / Slack webhooks: you pick the URL, you pick the secret. The platform (TrustBill here) just echoes your secret back on every call so you can prove it came from them.

Fields

FieldRequiredNotes
erpNameYese.g. "Acme ERP"
descriptionOptionalShort description shown to SMEs.
contactEmailYesWhere TrustBill support reaches you.
website, logoUrlOptionalDisplayed on the SME Sources card.
companyCountOptionalApproximate number of SMEs you serve.
testWebhookUrlOptionalYou provide. Public HTTPS URL of the endpoint YOUR ERP exposes to receive status updates. Cloud ERP by TrustBill: https://erp.trustbill.ae/trustbill/webhook. Others: your own path. Can be added later from the dashboard.
testWebhookSecretOptionalYou invent. Generate with openssl rand -hex 32. TrustBill echoes it back on every webhook as the x-webhook-secret header. Save the same value in your ERP's webhook verifier. Envelope-encrypted at rest.

What you get back

  • testApiKey — an erp_test_... string you send as X-API-Key.
  • revealedTestSecret — shown exactly once in the response. Store it — you send it as X-API-Secret. TrustBill only stores its scrypt hash; regenerate if lost.
  • status: "test" — you can start integration testing immediately.

Going live

When testing is done, click Request live access. A TrustBill admin reviews it. On approval you get a liveApiKey/liveWebhookSecret pair; your test credentials keep working the whole time so nothing breaks mid-transition.

الخطوة 2 — اتصال الشركات الصغيرة والمتوسطة

بمجرد تسجيلك (اختبار أو مباشر)، ترى كل شركة صغيرة ومتوسطة تابعة لشركة الاستشارات الخاصة بك ERP الخاص بك كبطاقة في صفحة مصادر TrustBill الخاصة بهم.

  1. SME opens their TrustBill portal → ERP integration.
  2. SME clicks Connect on your ERP card.
  3. TrustBill generates a per-SME secret tb_secret_<64 hex chars>, shows it once.
  4. SME copies it and pastes it into your ERP's "TrustBill Integration" settings — this is the secret your ERP will send on every invoice push.
  5. SME can rotate the secret at any time from the same page (invalidates the old one immediately).

Why per-SME: your API credential proves the caller is your ERP, but only tb_secret_... proves the push belongs to a specific SME. TrustBill uses it (not the TRN in the body) to resolve which SME the invoice belongs to; the TRN is cross-checked as defense-in-depth.

الخطوة 3 — إرسال الفواتير

عندما تنقر شركة صغيرة ومتوسطة على "Submit to FTA" داخل ERP الخاص بك، يرسل ERP الخاص بك الفاتورة إلى TrustBill. يتحقق TrustBill من المصادقة + الرقم الضريبي، يحفظ مسودة، يضيف إرسالًا تلقائيًا إلى هيئة الإمارات للضرائب/مزود الخدمة، ويعيد 201 مع معرف الفاتورة.

Endpoint

POST https://trustbill.ae/api/v1/partner/invoices

Headers

HeaderPurpose
X-API-KeyYour ERP's API key from Step 1 (erp_test_... or erp_live_...).
X-API-SecretYour ERP's API secret from Step 1. Verified against a scrypt hash.
x-erp-partner-secretThe tb_secret_... the specific SME pasted in Step 2. This is what identifies the SME.
Idempotency-KeyRequired. Unique per invoice (1–128 chars). Retrying with the same key returns the original result, never a duplicate invoice.
Content-Typeapplication/json

Body

Invoice data plus the SME's 15-digit TRN (cross-checked against the connection secret). Real, working example (copy-paste against a test partner):

{ "trn": "100000000000042", "number": "INV-2026-0001", "issueDate": "2026-08-01", "dueDate": "2026-08-15", "currency": "AED", "invoiceType": "sale", "seller": { "name": "TrustBill Test Co LLC", "trn": "100000000000003", "country": "AE", "address": { "line1": "Sheikh Zayed Road", "city": "Dubai", "emirate": "Dubai" }, "kind": "seller" }, "buyer": { "name": "Acme Trading LLC", "trn": "100000000000042", "country": "AE", "address": { "line1": "Marina Plaza", "city": "Dubai", "emirate": "Dubai" }, "kind": "buyer" }, "lines": [ { "description": "Consulting service, August", "quantity": 1, "unitPrice": 5000, "vatRate": 0.05, "taxCategory": "standard_5" } ], "documentType": "tax_invoice" }

Field reference

FieldTypeNotes
trn15-digit stringTop-level TRN — must match the SME resolved by x-erp-partner-secret. Cross-checked; mismatch → 403 trn_mismatch.
numberstring (1–64)Unique per (SME, mode). Duplicates return 500 today (planned 409 in a future release).
issueDatedate YYYY-MM-DDDate string, not ISO-8601 datetime. Dubai timezone recommended.
dueDatedate (optional)Same format as issueDate.
currency"AED"Only AED accepted in Phase 2a.
invoiceType"sale" | "purchase"Defaults to "sale".
seller.address.line1stringRequired when address is present. Same for buyer.address.line1.
seller.address.citystringRequired when address present.
seller.country, buyer.countryISO 2-lettere.g. "AE". Required.
lines[].unitPricedecimal AEDNot fils. A price of AED 5,000 is 5000, not 500000.
lines[].vatRatefraction 0-1Not percent. 5% VAT is 0.05, not 5.
lines[].taxCategoryenumstandard_5, zero_rated_export, zero_rated_healthcare, zero_rated_education, exempt_financial.
lines[]array1–200 items. Each requires description, quantity > 0, unitPrice ≥ 0.

Common validation gotchas

  • Never send null for optional fields. Zod on the server accepts absent (undefined) or a valid value, but rejects explicit null. If you don't have an email, omit the key — don't send "email": null.
  • Fils vs AED. Line prices are decimal AED. Sending 500000when you mean AED 5,000 will make a very expensive invoice.
  • Percent vs fraction. Standard VAT is 0.05, not 5. A rate of 5 fails the schema (vatRate ≤ 1).
  • Top-level trn is the SME's own TRN (the seller for sale invoices). TrustBill resolves your x-erp-partner-secret to an SME tenant, then compares body.trn to that SME's registered TRN. For invoiceType: "sale" this equals seller.trn; forinvoiceType: "purchase" it equals buyer.trn. Any other value → 403 trn_mismatch.
  • Date format for issueDate/dueDate. Send "2026-08-01", not "2026-08-01T00:00:00Z".

Success response

HTTP/1.1 201 Created { "invoiceId": "…", "number": "INV-2026-0001", "status": "queued" }

Example (curl)

curl -X POST https://trustbill.ae/api/v1/partner/invoices \ -H "X-API-Key: erp_live_..." \ -H "X-API-Secret: ..." \ -H "x-erp-partner-secret: tb_secret_..." \ -H "Idempotency-Key: acme-inv-2026-0001" \ -H "Content-Type: application/json" \ -d @invoice.json

Testing the body shape locally

Before wiring your ERP client, POST the body directly with curl against the test API. If it returns 201, your mapping is correct. If it returns 400 with meta.issues, each issue names the exact JSON path that failed — e.g. "buyer.email: invalid_type" means you sent null where the schema wants an omitted key. Fix the mapper before touching the queue/HTTP client code.

الخطوة 4 — استلام الحالة

عندما يستجيب هيئة الإمارات للضرائب/مزود الخدمة، يرسل TrustBill تغيير الحالة إلى عنوان URL لخطاف الويب الذي قمت بتكوينه في الخطوة 1. أطلق وانسَ: يرسل TrustBill مرة واحدة، لا يعيد المحاولة. إذا فاتك واحد، يمكنك دائمًا الاستقصاء عن الحالة عبر زر "Sync now" في ERP الخاص بك.

Request

POST <your webhook URL> x-webhook-secret: <the secret you registered in Step 1> Content-Type: application/json { "event": "invoice.status_changed", "erpPartnerId": "…", "invoiceId": "…", "status": "delivered" | "rejected", "reason": null | "asp_rejected: <fingerprint>" | "validation_failed: …", "timestamp": "2026-08-01T12:34:56.789Z" }

Fields

FieldMeaning
eventAlways "invoice.status_changed" for now.
erpPartnerIdYour ERP Partner id — the same one GET /v1/consultancy/erp-partner returned.
invoiceIdTrustBill's UUID for the invoice — matches the invoiceId from the Step 3 response.
status"delivered" = FTA/ASP accepted. "rejected" = failed validation or ASP rejection.
reasonnull when delivered. Short reason string when rejected.
timestampISO-8601 UTC.

Handler checklist

  • v1.5+: verify x-webhook-signature (HMAC) with the recipe in the HMAC section. Fall back to a constant-time compare of x-webhook-secret only when the HMAC header is absent (pre-v1.5 sender).
  • Return HTTP 200 (or any 2xx) quickly. TrustBill uses an 8s timeout; long processing should happen async.
  • Process idempotently — if TrustBill re-sends the same invoiceId + status (e.g. after a reconfigure), don't double-apply.
  • Follow redirects yourself if needed — TrustBill sends with redirect: error to avoid SSRF, so it will not auto-follow 3xx.
  • Missed a webhook? Poll GET /v1/partner/invoices/{id}as your fallback (see Polling section) — TrustBill fires webhooks once, no automatic retry.

الاحتياطي بالاستقصاء — v1.5+ v1.5+

عندما يكون خطاف الويب الخاص بك معطلاً، في منتصف الترحيل، أو إذا كنت تريد فقط فحصًا صحيًا، استقصي عن الحالة مباشرة. نفس المصادقة مثل POST — مفتاح API + سر بالإضافة إلى الرقم الضريبي للشركة الصغيرة والمتوسطة في الرأس.

Endpoint

GET https://trustbill.ae/api/v1/partner/invoices/{invoiceId}

Headers

X-API-Key: erp_live_... X-API-Secret: ... x-erp-partner-secret: tb_secret_...

Success response

HTTP/1.1 200 OK { "invoiceId": "88132d4d-56f0-4721-8a3a-079ac8d4a709", "number": "INV-2026-0001", "status": "delivered", "reason": null, "createdAt": "2026-08-04T05:19:21.403Z", "updatedAt": "2026-08-04T05:19:23.078Z" }

Same status vocabulary as the webhook. Reuse your webhook handler's switch statement; only the delivery mechanism differs. Statuses you'll see:queued, transformed, validated, blocked_no_credit, delivered, rejected. Both reason and humanMessage are surfaced from the last status change event.

404 semantics

HTTP/1.1 404 Not Found { "type": "https://docs.trustbill.ae/errors/not_found", "title": "Not Found", "status": 404, "code": "not_found", "detail": "No invoice with that id exists under your connected SME.", "request_id": "..." }

Same 404 response whether the id doesn't exist OR belongs to a different SME. This is intentional — we don't leak whether an ID exists under a partner you're not authorized for. Use detail for user-facing text.

whoami — التأمل الذاتي v1.5+ v1.5+

فحص صحي سريع لـ ERP الخاص بك: "هل أنا في وضع الاختبار أو المباشر، وكم عدد الشركات الصغيرة والمتوسطة المتصلة بنشاط الآن؟" المستخدم بواسطة زر "Test connection" في إضافة Cloud ERP.

Endpoint

GET https://trustbill.ae/api/v1/partner/whoami

Response

HTTP/1.1 200 OK { "partnerId": "9c8f06bf-00f6-4df6-be58-343ce48a1a86", "erpName": "Acme ERP", "status": "live_active", "mode": "live", "connectedSmes": 17, "lastTestAt": "2026-08-04T05:19:21.397Z", "timestamp": "2026-08-04T10:00:00.000Z" }

No secrets are ever returned. Safe to expose the response to your internal ops dashboard.

x-webhook-secret: <the secret you registered in Step 1> ← legacy x-webhook-timestamp: <unix seconds, e.g. 1725441234> ← v1.5 x-webhook-signature: t=<unix>,v1=<HMAC-SHA256(t + "." + body, secret) hex> ← v1.5 x-webhook-signature: t=<unix>,v1=<HMAC-SHA256(t + "." + body, secret) base64> ← v1.5

التحقق في ERP الخاص بك (Node.js)

import { createHmac, timingSafeEqual } from "node:crypto"; const MAX_SKEW_SECONDS = 300; // 5 minutes function verifyWebhook(req, storedSecret) { const header = req.headers["x-webhook-signature"]; if (!header) return false; const parts = Object.fromEntries( header.split(",").map(s => s.trim().split("=", 2)) ); const t = Number(parts.t); const sig = parts.v1; if (!t || !sig) return false; if (Math.abs(Date.now() / 1000 - t) > MAX_SKEW_SECONDS) return false; const rawBody = req.rawBody.toString(); // exact bytes as received const expected = createHmac("sha256", storedSecret) .update(`${t}.${rawBody}`) .digest("hex"); const a = Buffer.from(expected, "hex"); const b = Buffer.from(sig, "hex"); return a.length === b.length && timingSafeEqual(a, b); }

التحقق في PHP

$header = $request->header('x-webhook-signature'); $parts = []; foreach (explode(',', $header) as $seg) { [$k, $v] = explode('=', trim($seg), 2); $parts[$k] = $v; } $t = (int) ($parts['t'] ?? 0); $sig = $parts['v1'] ?? ''; if (abs(time() - $t) > 300) return abort(401); $expected = hash_hmac('sha256', $t . '.' . $request->getContent(), $storedSecret); if (!hash_equals($expected, $sig)) return abort(401);

Prefer HMAC over the legacy header. If HMAC verification fails, reject the request — do NOT silently fall back to x-webhook-secret. That would let an attacker who intercepted a plain-secret call replay it forever. Fall-back to plain-secret is only appropriate when the HMAC header is entirely absent (indicating a pre-v1.5 caller).

إضافة Cloud ERP (Workdo Dash)

إذا كنت تستخدم إضافة Cloud ERP الرسمية (TrustBillConnector)، فإن التحقق من HMAC مدمج من v1.5. فقط قم بتحميل ملف ZIP — لا شيء آخر لتكوينه.

أنواع المستندات v1.5+ v1.5+

حقل documentType الاختياري في نص POST يرسم إلى رموز نوع فاتورة FTA/UBL. الافتراضي هو tax_invoice (UBL 388) عند حذفه، لذا لا تحتاج إلى هذا لـ B2B الأساسي.

القيم المدعومة

documentTypeUBL codeUse when
tax_invoice388Standard B2B tax invoice (the default).
commercial_invoice380Non-VAT commercial invoice.
simplified_tax_invoice751B2C sale < AED 10,000.
credit_note381Refund / reversal — set precedingInvoiceReference.
debit_note383Additional charge — set precedingInvoiceReference.
self_billed_invoice389Buyer issues on behalf of the supplier.

precedingInvoiceReference

سلسلة اختيارية (1–64 حرفًا). مطلوب عندما يكون documentType هو credit_note أو debit_note. القيمة: invoiceId أو رقم الفاتورة التي تعدلها.

أرقام التعريف الضريبية للاختبار المحجوزة v1.5+ v1.5+

يتم احترامها فقط عندما يكون مفتاح API الخاص بك في وضع الاختبار (erp_test_...). استخدم هذه لممارسة كود المسار السعيد ومسار الرفض في ERP الخاص بك دون الحاجة إلى بيانات حقيقية للشركات الصغيرة والمتوسطة.

TRNBehaviour
100000000000000Always accepts. Skips the TRN cross-check against the SME row — useful for smoke tests where you don't want to set up an SME just to prove your HTTP client works. Still requires a valid x-erp-partner-secret.
100000000000666Always rejects. Returns 403 test_trn_reject. Lets you verify your ERP handles the error path — retry logic, user-facing error message, dashboard flag.
Live mode ignores these constants. Send them toerp_live_... and they get the standard TRN-mismatch treatment. There is no way to short-circuit real invoice validation in production.

الاختبار

  1. Register with test credentials (Step 1). Save revealedTestSecret.
  2. Stand up a public HTTPS webhook receiver (e.g. webhook.site for a first pass).
  3. On the ERP Partner dashboard, click Test integration. TrustBill will hit your webhook once with a sample payload (test: true) and confirm the round-trip.
  4. Sign in as an SME parented to your consultancy, go to ERP integration, click Connect, copy the tb_secret_....
  5. From your test harness, do a real POST /v1/partner/invoices push with test API credentials + the SME's connection secret.
  6. Watch your webhook for the status change once the (test) ASP responds.
  7. Check the Recent activity log on the ERP Partner dashboard — every ping, webhook test, and outcome is recorded.

ردود الأخطاء

تتبع الأخطاء RFC 7807 (problem+json). الرموز الشائعة على POST /v1/partner/invoices:

HTTPCodeWhen
401missing_api_credentialsNo X-API-Key / X-API-Secret headers.
401invalid_api_credentialsAPI key/secret don't match, or your integration hasn't been approved for live mode.
401missing_connection_credentialNo x-erp-partner-secret header.
401invalid_connection_credentialThe tb_secret_... doesn't match any active SME connection.
403connection_partner_mismatchSecret belongs to a different ERP Partner (never mix credentials).
403sme_not_connectedSME disconnected your ERP or the connection was revoked.
403trn_mismatchTRN in the body doesn't match the SME who owns the connection secret.
403test_trn_rejectv1.5+. You sent the reserved test TRN 100000000000666. Meant to help you test the rejection path.
404not_foundv1.5+ (on GET /v1/partner/invoices/{id}). Either the id doesn't exist, or it belongs to a different SME. detail field carries "No invoice with that id exists under your connected SME." — safe to display. Same response either way to avoid ID enumeration.
400idempotency_requiredMissing or malformed Idempotency-Key header.
400invalid_bodyBody failed schema validation. Details in meta.issues.

الانتقال إلى الوضع المباشر

  1. Finish integration tests end-to-end in test mode.
  2. On /erp-partner, click Request live access. Status moves to live_pending.
  3. A TrustBill admin reviews and either approves (mints erp_live_... credentials, copies your test webhook config to live) or rejects with a reason.
  4. Switch your ERP to use the live API key/secret. The x-erp-partner-secret per SME does not change.
  5. Test mode keeps working, so you can regression-test at any time without touching production.

الدعم