Перейти до вмісту

Накладення електронного підпису за допомогою Дія в Python

Матеріал з K2 ERP Wiki

payload=payload,

 request = signature_session.signature_request
{| class="wikitable"
 audit_logger.log(

1.; |-
| client_secret_encrypted
| text
| Зашифрований секрет.; |-
| Signature File
| Файл підпису або підписаний контейнер.; '''Критично істотно:''' якщо документ змінено після створення заявки на підпис, попередня заявка повинна бути скасована або переведена в статус INVALIDATED.; | Callback retry, polling статусу, журнал raw events.; |-
| signed_at
| timestamp
| Час підписання.; | style="background:#ffcc80;" | Помаранчевий
|-
| Помилка
| ERROR
| Технічна помилка.; K2 ERP отримує фінальний статус.; Поле
!; Diia Adapter
 callback_url: str

я хочу бачити callback-и, помилки API та технічний журнал, 

== 2.; Область сфера застосування ==
 return request
 "k2_entity_id": "contract-001"

 retry_backoff_seconds: int = 5

 except Exception as exc:
<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">
 client_id: str

 db=db,

 if payload.get("status") != "signed":
{| class="wikitable"
'''Головна ідея:''' розробити Python-сервіс, який надає можливість користувачам підписувати документи за допомогою '''Дія.Підпис''' із подальшим збереженням підписаного документа, файлу підпису, статусу підписання, журналу дій і результату перевірки підпису.; | style="background:#ef9a9a;" | Червоний
|-
| Не той документ
| HASH_MISMATCH
| Hash документа не збігається.; pass
 db=db,

 entity_id=request.id,

<syntaxhighlight lang="python">
 "document_number": "123",

 "deep_link": response.deep_link,

 v
!; |-
| entity_id
| uuid
| ID сутності.; | Попередня заявка стає INVALIDATED або скасовується.; | Він бачить документи, підписи, помилки, прострочення.; |-
| Document Service
| Робота з документами та версіями.; |-
| ключовий сценарій
| користувач системи відкриває QR/deep link, підтверджує підписання в застосунку, платформа отримує результат.; |-
| Callback
| callback_id, raw payload, статус перевірки.; |-
| Отримання підпису
| file_id, hash підпису, час.; |-
| status
| varchar
| Статус заявки.; | платформа повертає помилку і записує подію.; {| class="wikitable"

* реалізувати завантаження документа;
* реалізувати версіонування;
* реалізувати hash;
* реалізувати валідацію;
* реалізувати дедублікацію.; # Який точний формат результату підписання: p7s, ASIC, PDF з підписом або інший?; |-
| Callback Event
| Подія, отримана від Дії.; Дата
 |
 | 4.; KPI
 document_version = document_version_repository.get_by_id(db, request.document_version_id)
!; Показник
 )
!;</pre>
 request.status = "VERIFIED"
{| class="wikitable"
!; HTML

 result = signature_verifier.verify(
=== 23.2.; Створення сесії Дія.Підпис ===
!; |-
| document_number
| varchar
| Номер документа.; |-
| created_at
| timestamp
| Дата створення.; Signature Storage зберігає підпис.; * Офіційна сторінка Дія.Підпис для партнерів.; document_version = document_version_repository.get_by_id(db, request.document_version_id)
платформа повинна логувати:
Перед створенням заявки платформа повинна перевірити:
[[Категорія:Python]]
!; Для багатьох КЕП-сценаріїв типовим є собою окремий файл підпису або контейнер.; |-
| raw_request
| jsonb
| Запит до Дії.; # Чи потрібно підписувати PDF, XML, DOCX або будь-який файл?; # Чи потрібна авторизація через Дія.Підпис?;== 26.; Dashboard керівника ==
DIIA_SIGNATURE_CLIENT_SECRET=********
 "raw_response": response.raw_payload,
=== 14.1.; Створення інтеграції ===
{| class="wikitable"
== 31.; Етапи реалізації ==
!; |-
| SessionExpiredError
| Сесія підписання прострочена.; Поле
 def create_signature_session(self, payload: "CreateSignatureSessionPayload") -> "SignatureSessionResponse":
!; Очікуваний результат

== 24.; Обробка помилок ==

 entity_type="signature_request",

 if callback_repository.exists(callback_id):

{| class="wikitable"

* створення заявки на підписання документа;
* підготовку документа до підпису;
* розрахунок hash документа, якщо це вимагається інтеграцією;
* створення сесії підписання;
* генерацію QR-коду або deep link для переходу в застосунок Дія;
* відображення користувачу статусу підписання;
* отримання callback / webhook від Дії;
* отримання результату підписання;
* збереження підпису;
* збереження підписаного документа або контейнера;
* перевірку підпису;
* перевірку цілісності документа;
* актуалізація статусу документа в K2 ERP або іншій системі;
* журналювання всіх подій;
* контроль помилок;
* dashboard для відповідальних осіб.; Причина
|-
| Прийом callback
| Критичний
| Не можна втрачати результат підписання.; Тип
 "raw_request": payload,
{| class="wikitable"
POST /api/v1/diia-signature/documents/{document_id}/signature-requests

{{SEO
|title=Технічне завдання: Накладення електронного підпису за допомогою Дія.Підпис у Python
|description=Технічне завдання на реалізацію Python-сервісу для інтеграції з Дія.Підпис: підписання документів, авторизація, QR/deep link, статуси, callback, p7s, перевірка підпису, журналювання, dashboard та безпека.
|keywords=Python, Дія.Підпис, Diia.Signature, КЕП, електронний підпис, підписання документів, FastAPI, K2 ERP, callback, p7s, електронний документообіг
}}
 "document_date": "2026-05-07",
5.; Результат
</pre>
 "signer_id": command.signer_id,
</pre>
 payload={"external_document_id": command.external_document_id},
{| class="wikitable"

щоб оперативно знаходити причини невдалого підписання.; Пріоритет
{| class="wikitable"
payload={"signature_request_id": str(request.id)},

17.; Hash документа і версії

7.3.; Адміністратор перевіряє помилки

Для кожного документа потрібно зберігати: Ключі дедублікації:

request.status = "SIGN_ERROR"


def verify_signature(signature_request_id: str, signature_file_id: str, db: "Session") -> None:

db.commit()
style="background:#eeeeee;" | Сірий
Готовий до підпису READY_TO_SIGN - expires_at timestamp Дата завершення.; Очікуваний результат

13.; Конфігурація

"signature_request_id": request.id,
 signature_session=signature_session,
щоб підписати документ без завантаження ключів у систему.; |-
| AuthError
| Невірні credentials Дії.;[[Категорія:K2 ERP]]

Після отримання результату підписання платформа повинна виконати перевірку.; |-
| is_active
| boolean
| Активність.; | style="background:#ef9a9a;" | Червоний
|-
| Потребує ручної перевірки
| MANUAL_REVIEW
| Неможливо автономно визначити результат.; |-
| AC-6
| Документ змінено після створення заявки.; Поле
K2 ERP / CRM / Website
!;<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">

* Офіційна сторінка інтеграції Дії.; характеристика

== 34.; Джерела ==
{| class="wikitable"
=== 7.2.; Менеджер контролює підписання ===
=== 14.4.; Створення заявки на підпис ===

 raise HTTPException(status_code=401, detail="Invalid callback signature")
 "raw_result": result.raw,
=== Етап 6.; Перевірка підпису ===
=== 14.8.; Завантаження підписаного документа ===
користувач системи відкриває документ у K2 ERP або на сайті, натискає кнопку «Підписати через Дія.Підпис».; |}

<pre>

!; request.status = "DECLINED_BY_USER"
 "certificate_info": result.certificate_info,
== 27.; Безпека ==
 finally:
!; request=request,

 "signature_request_id": request.id,

 external_document_id=command.external_document_id,

* створити пакет документів;
* перевірити всі документи;
* передати пакет на підписання, якщо це підтримується;
* отримати результат по кожному документу;
* показати частково підписані або помилкові документи;
* не втратити статус окремого документа.; Verification Service перевіряє підпис.;== 23.; Приклад Python-логіки ==

</pre>
 client_secret: str
 document_version=document_version,
from pydantic_settings import BaseSettings
|-
| Немає партнерського доступу
| Без доступу неможливо реалізувати production інтеграцію.; | Помилка підписання, помилка перевірки.; |-
| qr_payload
| text
| інформаційні дані QR, якщо зберігаються.; |-
| AC-8
| користувач системи підписує документ.; * створення інтеграції Дія.Підпис;
* перевірка підключення;
* створення документа;
* збереження версії документа;
* розрахунок hash;
* створення заявки на підпис;
* створення сесії підписання;
* QR/deep link;
* callback endpoint;
* збереження результату підписання;
* базова перевірка підпису;
* статуси документа;
* журнал подій;
* dashboard API;
* retry для технічних помилок;
* ідемпотентність callback;
* unit-тести;
* mock Diia client.; характеристика

 verification_queue.enqueue(
 "diia_session_id": response.session_id,
{| class="wikitable"

платформа:

!; # Який строк дії сесії підписання?; |-
| idempotency_key
| Унікальний ключ заявки.; | Статус стає VERIFY_ERROR.; |-
| current_version_id
| uuid
| Поточна реліз.; Signature Storage + Verification Service

 "idempotency_key": command.idempotency_key,

== 15.; Приклад запиту на створення заявки на підпис ==

* створює authorization session;
* показує QR/deep link;
* отримує підтвердження;
* ідентифікує користувача згідно з дозволеним обсягом даних;
* створює або оновлює сесію користувача.; API Дії / QR / deep link
<div style="border-left: 6px solid #f57c00; background: #fff3e0; padding: 12px 16px; margin: 16px 0;">
!; | Статус стає DECLINED_BY_USER.; # Чи потрібні email/SMS-нагадування?; |-
| Відкриття QR/deep link
| request_id, час.; | Статус стає VERIFIED.; !; | Не створювати сесію.; | Вони підсвічуються фіолетовим.; |-
| FileTooLargeError
| Документ перевищує ліміт.; Очікуваний результат
 session_ttl_minutes: int = 15

=== 14.5.; Отримання QR/deep link ===

!; |-
| style="background:#eeeeee;" | Сірий
| #eeeeee
| Чернетка або архів.; | DRAFT, archived.; |-
| entity_type
| varchar
| document, request, session, callback, verification.; |-
| idempotency_key
| varchar
| Ключ дедублікації.; | Уточнити формат за документацією Дії.; Призначення
До MVP не входить:
 )
 )
Callback URL Python-сервісу
<pre>
{| class="wikitable"
base_url: str
- Створення сесії Високий ключовий сценарій користувача.; Дія системи

Співробітник компанії підписує внутрішній документ.; | платформа повертає успішний або помилковий статус.; | style="background:#c8e6c9;" | Зелений

Відхилено користувачем DECLINED_BY_USER - callback_event_id ID callback-події, якщо надається.; характеристика
Валідний VALID Зупинити інтеграцію і повідомити адміністратора.; |}

Етап 7.; Dashboard та аудит

from hashlib import sha256

  • timeout;
  • HTTP 429;
  • HTTP 500;
  • HTTP 502;
  • HTTP 503;
  • HTTP 504;
  • тимчасової помилки створення сесії;
  • тимчасової помилки отримання статусу;
  • тимчасової помилки перевірки підпису;
  • повторного callback з тим самим callback_id.; |-
Verification Service class="wikitable"
  • реалізувати callback endpoint;
  • реалізувати перевірку callback;
  • реалізувати збереження результату;
  • реалізувати ідемпотентність;
  • реалізувати raw event storage.; # Чи потрібно зберігати підписані документи в архіві довгострокового зберігання?; Результат підписання
callback_event = callback_repository.create_raw_event(payload)
Вони підсвічуються помаранчевим.; |- created_at timestamp - Прострочені сесії користувач системи не завершив підписання.; Значення
)
- Callback Controller - Помилка перевірки - AC-5 Версіонування і hash документа.; {| class="wikitable" ;

18.; Callback від Дії

; Користувачу показується QR/deep link.; Збереження, перевірка, статус
POST /api/v1/diia-signature/callback
=== 23.1.; Створення заявки на підпис ===

'''Заборонено:''' зберігати client_secret, приватні ключі, токени, callback secrets або інші секрети у коді, Git-репозиторії, frontend-змінних або відкритих логах.; |-
| Збереження підпису
| Критичний
| Юридично значущий результат.; |-
| signature_request_id
| uuid
| Заявка.; Документ на підпис
!; |-
| AC-12
| Hash документа не збігається.; | Перевірка даних сертифіката.; |-
| Audit Event
| Подія журналу.; pass

 },

 new_status="CREATING",

* відкриває сторінку підписання;
* показує коротку інформацію про документ;
* показує QR/deep link;
* замовник підтверджує підписання в Дії;
* платформа отримує результат;
* документ стає підписаним клієнтом.; |-
| mime_type
| MIME type.; |-
| signer_name
| varchar
| ПІБ підписанта з сертифіката.; |-
| created_at
| Дата створення версії.; Створюється signature_request.; |-
| AC-14
| Callback має правильний підпис/секрет.; |-
| style="background:#f3e5f5;" | Фіолетовий
| #f3e5f5
| Ручна перевірка або нестандартний сценарій.; * Партнерська API-документація Дії, яка надається після підключення.; |-
| document_version_id
| uuid
| реліз документа.; | style="background:#c8e6c9;" | Зелений
|-
| Підпис перевірено
| VERIFIED
| Підпис пройшов перевірку.; |-
| Невідповідність підписанта
| Документ підписала не та особа.;
; №

22.8. signature_events

DIIA_SIGNATURE_SESSION_TTL_MINUTES=15

; Критерій

21.2.; Пріоритети задач

router = APIRouter()

9.; Статуси сесії підписання

data={

Що таке Дія.; 3.Підпис у межах інтеграції

}
"status": "CREATING",
v
id uuid ID документа.; характеристика
  • приймати тільки HTTPS-запити;
  • перевіряти підпис або секрет callback, якщо передбачено API;
  • перевіряти session_id;
  • перевіряти request_id;
  • перевіряти idempotency callback;
  • зберігати raw payload;
  • оновлювати статус сесії;
  • зберігати файл підпису або посилання на результат;
  • запускати перевірку підпису;
  • повертати коректний HTTP status.; характеристика
Python Diia.Sign Integration Service
== 28.; Логування та аудит ==

{| class="wikitable"

Застосунок Дія
 "file_id": "file-001",
__TOC__
=== Етап 8.; Production hardening ===
 max_document_size_mb: int = 10
8.; |-
| style="background:#ffcc80;" | Помаранчевий
| #ffcc80
| Потрібна дія або є собою ризик.; |-
| redirect_url
| varchar
| URL повернення користувача.; 
async def create_diia_signature_session(signature_request_id: str, db: "Session") -> None:
 return {"status": "already_processed"}
 signature_session.status = "DECLINED"
[[Категорія:Електронний документообіг]]
<pre>
!; |-
| created_at
| timestamp
| Дата перевірки.; Створюється signature_session.; я хочу бачити кількість документів на підписі, підписаних, відхилених і прострочених, 
 "external_document_id": "K2-DOC-2026-000123",
 except Exception as exc:
|-
| Створення документа
| Тип, номер, реліз, hash.; |-
| AC-16
| Callback невалідний.; | style="background:#ffcc80;" | Помаранчевий
|-
| Помилка підписання
| SIGN_ERROR
| Помилка під час підписання.; |-
| Signer
| Підписант.; Очікуваний результат

* створює запис документа;
* створює сесію підписання;
* генерує QR/deep link;
* показує QR користувачу;
* очікує callback;
* отримує результат;
* зберігає підпис;
* перевіряє підпис;
* змінює статус документа на SIGNED.; |-
| SignatureResultError
| Не вдалося отримати результат підпису.; |-
| TimeoutError
| API недоступне або timeout.; |-
| SignerMismatchError
| Підписант не відповідає очікуваному.; Python-сервіс перевіряє документ.; |-
| Перевірка підпису
| Високий
| Потрібна для фінального статусу.; GET /api/v1/diia-signature/signature-requests/{request_id}/status
!; | style="background:#e3f2fd;" | відомості
|-
| Очікують підпису
| Документи з активною сесією.; |-
| Нагадування про прострочення
| Низький
| Фоновий бізнес-процес.; | платформа не дублює результат.; |-
| AC-9
| користувач системи відхиляє підписання.; | платформа показує QR/deep link.; Статус
 audit_logger.log(

!; "file_name": "contract_123.pdf",

 data={
=== 11.2.; Основні компоненти Python-сервісу ===
</pre>


!; | Отримати доступ до старту розробки.; Окремо варто відзначити збереження підпису і успішної перевірки цілісності.; |-
| provider
| varchar
| diia_signature.; * невалідного документа;
* документа, який змінився;
* простроченої сесії;
* відхилення користувачем;
* невірного callback signature;
* невідповідності підписанта;
* вже фінального статусу VERIFIED.; Тип
|-
| API Layer
| REST API для створення заявок на підпис.; |-
| актуалізація dashboard
| Середній
| Контроль.; Стан
!; |-
| created_at
| timestamp
| Дата створення.; |}

'''Критично істотно:''' без офіційної документації партнера Дії не можна фіксувати production endpoint-и, назви параметрів і формат callback як остаточні.; характеристика
DIIA_SIGNATURE_CLIENT_ID=********
 payload={"error": str(exc)},
!; | style="background:#ffcc80;" | Помаранчевий
|-
| Прострочено
| EXPIRED
| Строк сесії підписання минув.; | MANUAL_REVIEW.; # Чи потрібне пакетне підписання?; |-
| AC-15
| Callback повторився.; Задача
 )
|-
| AC-11
| Підпис валідний.; | style="background:#ffcc80;" | Потрібна дія
|-
| Прострочено
| Сесія не завершена вчасно.; Критерій

 redirect_url: str | None = None
!; |-
| document_id
| uuid
| Документ.; !; | Статус MANUAL_REVIEW або VERIFY_ERROR.; |-
| file_name
| varchar
| Назва файлу.; характеристика

* отримати партнерську документацію;
* отримати тестові credentials;
* погодити callback URL;
* перевірити тестовий сценарій;
* визначити формат результату підписання;
* визначити правила перевірки підпису.; Поле
 new_status="SIGN_ERROR",
|-
| id
| uuid
| ID перевірки.; Python-сервіс повинен сам зберігати документ, підпис, статус, аудит і результат перевірки.; |-
| version_number
| integer
| Номер версії.; |-
| source
| varchar
| K2_ERP, PYTHON_SERVICE, DIIA, USER.; |-
| created_at
| timestamp
| Дата створення.;=== 14.11. Dashboard ===

* HTTPS для всіх endpoint-ів;
* перевірку SSL;
* зберігання секретів тільки в secret storage;
* шифрування файлів підпису;
* шифрування документів або контроль доступу до них;
* обмеження доступу до callback endpoint;
* перевірку callback signature / secret;
* ідемпотентність callback;
* журнал усіх дій;
* маскування персональних даних у логах;
* контроль доступу до документів;
* окремі права на створення заявки;
* окремі права на повторне підписання;
* окремі права на ручну перевірку;
* заборону підписання зміненої версії документа.; характеристика
[[Категорія:API]]
 )
=== 23.4.; Обробка результату підписання ===
 data={

 if payload.get("status") == "declined":

{| class="wikitable"

* наявність external_document_id;
* наявність idempotency_key;
* наявність файлу документа;
* файл доступний у сховищі;
* файл не порожній;
* розмір файлу не перевищує ліміт;
* MIME type дозволений;
* документ не був змінений після створення заявки;
* hash документа збережений;
* підписант визначений;
* строк підписання не минув;
* документ ще не підписаний цим підписантом;
* бізнес-процес надає можливість підписання;
* користувач системи має право ініціювати підписання.; # Який callback security mechanism надає Дія?; | style="background:#ffcc80;" | Потрібна дія
|-
| Помилки
| Помилки підписання або callback.; |-
| Document Version
| реліз документа, яка передана на підпис.; | style="background:#c8e6c9;" | Зелений
|-
| Невалідний
| INVALID
| Підпис не пройшов перевірку.; Поле

 audit_logger.log(
<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">
def create_signature_request(command: "CreateSignatureRequestCommand", db: "Session") -> "SignatureRequest":
=== 5.3.; Підписання документа клієнтом ===

</pre>
<syntaxhighlight lang="python">
</div>
Як керівник, 
=== 22.5. signature_sessions ===

 "signature_request_id": str(request.id),

!;</div>

 "idempotency_key": "K2-DOC-2026-000123-sign-v1",

 pass
 )
=== 22.7. signature_verifications ===
|-
| Тип сервісу
| Електронний підпис через застосунок Дія.; |-
| document_date
| date
| Дата документа.; |-
| created_by
| uuid
| Хто створив заявку.; Критерій
|-
| AC-1
| Адміністратор створює інтеграцію Дія.Підпис.; | style="background:#bbdefb;" | Блакитний
|-
| Підписано
| SIGNED
| Підпис успішно отримано і збережено.; |}

{| class="wikitable"

=== 26.2.; Приклад dashboard ===
 )
!; |-
| Перевірка підпису
| результат, підписант, сертифікат.;<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">

<pre>

=== 5.5.; Авторизація через Дія.Підпис ===
[[Категорія:Дія.Підпис]]
!; | інтеграційні функціональні можливості зберігається в системі.; Реальні endpoint-и і payload потрібно взяти з офіційної документації Дії для партнера.; DIIA_SIGNATURE_RETRY_COUNT=3
платформа повинна забезпечити:
 )

{

{| class="wikitable"

== 21.; Черга обробки ==

* цілісність документа;
* відповідність підпису конкретній версії документа;
* валідність підпису;
* валідність сертифіката;
* інформаційні дані підписанта;
* час підписання;
* статус відкликання сертифіката, якщо доступно;
* чи відповідає підписант очікуваному користувачу;
* чи не минув строк сесії;
* чи не змінювався документ після підпису.; |}

4.; | style="background:#fff9c4;" | Увага
|-
| Підписано
| Підпис отримано.; | style="background:#ffcc80;" | Помаранчевий
|-
| Прострочена
| EXPIRED
| Сесія не завершена у строк.; Сутність

 session = signature_session_repository.create(

=== Аналіз інтеграції Дія.; Етап 1.Підпис ===

!; |-
| base_url
| varchar
| URL API.; Статус
=== 29.2.; Документ ===
 "result": result.code,
'''Управлінський результат:''' відповідальна особа повинна бачити, які документи очікують підпису, які підписані, які відхилені, які прострочені, які мають помилки callback, які потребують повтору або ручної перевірки.; Критерій

* реалізувати Verification Service;
* реалізувати статуси перевірки;
* реалізувати ручну перевірку;
* реалізувати журнал перевірок.; характеристика
=== 29.5. Callback ===

 },
 task_name="verify_signature",
 signature_queue.enqueue(

 verify_ssl: bool = True
=== Етап 2.; Базовий Python-сервіс ===
замовник отримує посилання на документ.;=== 14.10.; Перевірка підпису ===

Приклад `.env`:

 db.commit()
@router.post("/api/v1/diia-signature/callback")
== 30. MVP ==

GET /api/v1/diia-signature/signature-requests/{request_id}/link

</div>
Перевіряється:
=== 7.4.; Керівник бачить dashboard ===
db=db,
return

Callback endpoint повинен:

"email": "client@example.com",

5.4.; Підписання документа співробітником

K2 ERP / Dashboard / електронний документообіг

"qr_payload": response.qr_payload,

21.1.; Логіка черги

Етап 4.; Документи

"full_name": "Іван Петренко",
event_type="DIIA_SIGNATURE_SESSION_ERROR",

щоб знати, чи замовник підписав документ.; |-

created_at timestamp - Невідомий формат підпису - file_hash_sha256 varchar - file_hash_sha256 varchar style="background:#ef9a9a;" | Червоний
entity_type="signature_request",

користувач системи підписує декілька документів за один бізнес-процес.; callback_id = callback_service.get_callback_id(payload)

щоб контролювати електронний документообіг.; !; |-

Signature Session - created_at timestamp - file_id uuid style="background:#ef9a9a;" | Критично
Ручна перевірка Показати користувачу помилку.; характеристика
"signed_at": result.signed_at,
request.status = "SIGNED"
|-
| id
| uuid
| ID інтеграції.; |}

 )
До MVP входить:
<pre>

  • партнерський доступ до інтеграції Дії;
  • офіційну документацію Дія.Підпис;
  • тестове середовище, якщо доступне;
  • client_id або аналогічний ідентифікатор партнера;
  • client_secret або інший секрет доступу;
  • сертифікати, якщо вони потрібні для взаємодії;
  • endpoint-и API Дії;
  • callback URL, який буде приймати результат;
  • правила формування QR/deep link;
  • правила формування запиту на підпис;
  • допустимі формати документів;
  • максимальний розмір документа;
  • правила зберігання результату підписання;
  • правила перевірки підпису;
  • контакт технічної підтримки Дії.; | TTL, нагадування, повторна заявка.; |}
DIIA_SIGNATURE_REDIRECT_URL=https://example.com/signature/result

Diia Client — це Python-клас або пакет, який інкапсулює роботу з API Дія.Підпис.; |-

Дублювання callback має змогу повторно змінити статус.; def get_signature_result(self, session_id: str) -> "SignatureResultResponse": - document_version_id реліз документа.;

16.; Валідація документа перед підписом

)
Signature Integration конфігурація підключення до Дія.Підпис.; Критерій - raw_response jsonb Відповідь Дії.; return {"status": "ok"}
if existing:
id uuid - diia_session_id - file_hash_sha256 Hash файлу.; Тип

14.; API Python-сервісу

AC-17 - client_id varchar - file_size Розмір файлу.; Статус

1.; Мета

Документів створено - Signature Request Service Скасувати заявку або створити нову.; |- signer_id - Verification Result - file_hash_sha256 Hash документа.; №
def authenticate(self) -> "AuthResult":
; характеристика

Етап 3.; Diia Client

pass

22.1. diia_signature_integrations

Перевести в SIGN_ERROR або NEEDS_RETRY.; | style="background:#fff9c4;" | Жовтий
Очікує callback WAITING_CALLBACK користувач системи перейшов у Дію, платформа очікує результат.; idempotency_key=command.idempotency_key,

def process_signature_result(signature_session: "SignatureSession", payload: dict) -> None: class DiiaSignatureSettings(BaseSettings): GET /api/v1/diia-signature/documents/{document_id}/signed-file

Чернетка DRAFT - Жовтий #fff9c4 Очікування дії користувача або callback.; Тип - QR / Deep Link Service Відхилено, прострочено.; |}
 signature_file = signature_file_repository.get_by_id(db, signature_file_id)
 return
!; 
=== Етап 5.; Callback та підпис ===

<pre>
=== 22.6. signature_files ===
== 12. Diia Client ==

=== 5.2.; Підписання пакета документів ===

== 10.; Єдина логіка кольорів ==
<syntaxhighlight lang="python">


 "document_id": document.id,
Для реалізації задачі необхідно отримати:
 db=db,
 signature_request_id=request.id,
Дія.Підпис у межах цього ТЗ розглядається як зовнішній сервіс, який надає можливість користувачу підтвердити свою дію та накласти електронний підпис через застосунок Дія.; | Створення сесії, підписання.; |-
| Signature Storage
| Зберігання підпису, контейнера, документа.; |-
| mime_type
| varchar
| MIME type.; |}

GET /api/v1/diia-signature/documents/{document_id}/signature-file

== 25.; Retry-логіка ==

DIIA_SIGNATURE_BASE_URL=https://partner-api.example.diia
Як менеджер, 
 |
 | 1.; !; Підписант
Як адміністратор, 
 if not signature_session:

<pre>
!; # Чи потрібно підписувати документи клієнтами, співробітниками або обома?; |-
| Signature Request
| Заявка на підписання.; |}

 signature_file = signature_storage.save_signature_result(

 response = await diia_client.create_signature_session(payload)
 audit_logger.log(
!; | Очікує підпису, активна сесія.; |-
| signature_request_id
| uuid
| Заявка.; |}

 expected_hash=document_version.file_hash_sha256,

 )
 payload={"error": str(exc)},
{| class="wikitable"
 task_name="create_diia_signature_session",
|-
| id
| uuid
| ID сесії.; Не можна підписувати старий hash для нової версії документа.; | style="background:#c8e6c9;" | Норма
|-
| Перевірено
| Підпис пройшов перевірку.; | style="background:#ef9a9a;" | Червоний
|-
| Помилка перевірки
| VERIFY_ERROR
| Підпис отримано, але перевірка не пройдена.; |-
| updated_at
| timestamp
| Дата актуалізація.; |}

 payload=payload,

# Чи вже є собою партнерський доступ до Дія.Підпис?; | style="background:#bbdefb;" | Блакитний
|-
| Активна
| ACTIVE
| QR/deep link доступний користувачу.; |-
| external_document_id
| varchar
| ID документа в K2 ERP.; характеристика
sha256(file_bytes)

 },

9.; Значення

 request.status = "MANUAL_REVIEW"

* створює задачу на підпис;
* показує її у списку задач;
* контролює строк підписання;
* нагадує про прострочення;
* зберігає аудит дій.; Колір
</div>

 new_status="ACTIVE",
payload = diia_mapper.to_signature_session_payload(
- expires_at timestamp - signer_id uuid - AC-20 - AC-3 Credentials неправильні.; характеристика
# Перевірка callback signature / secret залежить від офіційної документації Дії.; |-
file_size integer }
"tax_id": "1234567890"
Заявка не створюється.; pass
  • реалізувати dashboard API;
  • реалізувати список проблемних документів;
  • реалізувати фільтри;
  • реалізувати експорт, якщо потрібно.; Колір

29.3.; Підписання

8.; Статуси документа

29.4.; Перевірка

ValidationError - Document Документ, який потрібно підписати.; характеристика

Технічний стек: Python 3.11+, FastAPI, PostgreSQL, SQLAlchemy, Alembic, httpx, Pydantic, Celery/RQ/APScheduler, Redis, Docker, S3-compatible file storage.; Код

14.9.; Завантаження файлу підпису

Приклад hash:

}
retry_count: int = 3

інтеграційні функціональні можливості має змогу використовуватись для:

event_type="SIGNATURE_REQUEST_CREATED",
verification = signature_verification_repository.create(
id uuid - Червоний #ef9a9a - signature_request_id uuid Заявка.; Колір

платформа повинна:

"file_mime_type": "application/pdf",

</syntaxhighlight> class DiiaSignatureClient:

!; Валідація, hash, створення сесії
 },
<pre>
 |
 | 3.; !; Параметр
 try:
 |
 | 2.; Колір
=== 29.1.; інтеграційні функціональні можливості ===
 signature_session.status = "COMPLETED"
|-
| AC-4
| Документ валідний.; |}

 entity_id=request.id,

 pass
 if not callback_security_service.is_valid(request, payload):
 try:
платформа:
|-
| id
| uuid
| ID файлу підпису.; * офіційно затверджений FAQ Дія.Підпис.; |}

 "signer_identifier": result.signer_identifier,

2.;</div>
!; |-
| signer_identifier
| varchar
| Ідентифікатор підписанта, якщо доступний.; | платформа приймає callback.; # Чи потрібна інтеграційні функціональні можливості з ЕДО-системами після підписання?; |-
| AC-2
| Адміністратор перевіряє підключення.; !; |-
| raw_result
| jsonb
| Повний результат перевірки.; Можливі результати:

!; Тип
 "signature_file_id": str(signature_file.id),
|-
| AC-7
| користувач системи натискає «Підписати через Дія».; |-
| Помилка
| код, повідомлення, stack trace без секретів.; | Ідемпотентність callback.; | style="background:#c8e6c9;" | Зелений
|-
| Відхилена
| DECLINED
| користувач системи відхилив дію.; |-
| Формат результату
| Уточнюється за офіційною документацією Дії.; Ключ
 "callback_context": {
 )
!; | Відхилити callback і записати подію.; |-
| callback_url
| varchar
| Callback URL.; !; Поле
!; | style="background:#bbdefb;" | Блакитний
|-
| Очікує підпису
| WAITING_SIGNATURE
| Користувачу створено QR/deep link.; | платформа створює заявку на підпис.; v
|-
| 07.05.2026
| Договір №123
| Іван Петренко
| style="background:#ffcc80;" | Прострочено
| користувач системи не завершив підписання
| Створити нову заявку
|-
| 07.05.2026
| Акт №45
| Олена Сидоренко
| style="background:#ef9a9a;" | Помилка перевірки
| Hash документа не збігається
| Ручна перевірка
|-
| 07.05.2026
| Заява №77
| ТОВ «Альфа»
| style="background:#f3e5f5;" | Ручна перевірка
| Неможливо автономно визначити підписанта
| Перевірити сертифікат
|}

Метою задачі є собою створення Python-сервісу для накладення електронного підпису за допомогою Дія.Підпис.; request = signature_request_repository.get_by_id(db, signature_request_id)

* додати rate limiting;
* додати alerting;
* додати dead letter queue;
* додати backup файлів;
* додати моніторинг callback;
* додати безпечне зберігання секретів.; | MANUAL_REVIEW і аудит.; №

!; | style="background:#f3e5f5;" | Фіолетовий
|}

{| class="wikitable"

POST /api/v1/diia-signature/documents
[[Категорія:КЕП]]
 finally:
 v
{| class="wikitable"
|-
| Створюється
| CREATING
| платформа створює сесію підписання.; |}

!; !; |-
| AC-18
| є собою помилки підписання.; Критерій
=== 22.4. signature_requests ===
 "document_version_id": document.current_version_id,
|-
| document_version_id
| ID версії документа.; | style="background:#ef9a9a;" | Червоний
|-
| Не той підписант
| SIGNER_MISMATCH
| Підписант не відповідає очікуваному.; Код

 return {"status": "unknown_session"}
 "status": "ACTIVE",
!; |-
| Audit Logger
| Журнал подій, callback-ів, помилок.; характеристика

!; '''істотно:''' точні endpoint-и, формат callback, формат підписаного контейнера, параметри deep link / QR та правила взаємодії потрібно брати з офіційної документації Дії, яку надають після підключення партнера.; №

GET /api/v1/diia-signature/dashboard?date_from=2026-05-01&date_to=2026-05-31
== 19.; Перевірка підпису ==
<div style="border-left: 6px solid #c62828; background: #ffebee; padding: 12px 16px; margin: 16px 0;">
!; |-
| certificate_info
| jsonb
| інформаційні дані сертифіката.; характеристика
DIIA_SIGNATURE_CALLBACK_URL=https://example.com/api/v1/diia-signature/callback
</div>

</div>
Retry заборонений для:
 signature_validator.validate_document_for_signing(document, command)
Сервіс повинен забезпечити:
 "external_signer_id": "CLIENT-001",
платформа:

!; Callback Controller приймає результат.; |-
| new_status
| varchar
| Новий статус.; def cancel_signature_session(self, session_id: str) -> "CancelSignatureSessionResponse":
=== 29.6. Dashboard ===
</div>
<div style="border-left: 6px solid #f57c00; background: #fff3e0; padding: 12px 16px; margin: 16px 0;">
=== 11.1.; Загальна схема ===
 request.status = "VERIFY_ERROR"
POST /api/v1/diia-signature/integrations/{integration_id}/check-connection
 )

POST /api/v1/diia-signature/documents/{document_id}/verify

!; | style="background:#fff9c4;" | Жовтий
|-
| Підписується
| SIGNING
| користувач системи відкрив бізнес-процес підписання.; # Чи потрібен UI для підписанта?; |-
| DocumentChangedError
| Документ змінено після заявки.; |-
| created_at
| timestamp
| Дата створення.; |-
| result
| varchar
| VALID, INVALID, HASH_MISMATCH тощо.;=== 14.2.; Перевірка підключення ===
 request = signature_request_repository.create(

!; |-
| payload
| jsonb
| Технічні інформаційні дані.; | Статус стає EXPIRED.; Поле

22.; Модель даних

else:
signature_file_id=signature_file.file_id,
)
"document_name": "Договір поставки №123",

істотно: назви методів у Python-клієнті є собою внутрішньою абстракцією.; * Документація K2 ERP щодо документів і бізнес-процесів.; |-

name varchar }
def get_signature_session_status(self, session_id: str) -> "SignatureSessionStatusResponse":
v
entity_type="signature_session",

3.; |-

Результат Підпис, підписаний контейнер або інформаційні дані підписання згідно з API Дії.; Тип помилки
},
"expires_at": command.expires_at,
signature_session.status = "ERROR"

6.; |}

;== 20.; Дедублікація == - Створення заявки - Callback втрачено платформа показує AuthError і не створює сесії.; |- file_id uuid }

7. User Story

"signer_name": result.signer_name,
!; | Retry, якщо безпечно.; | Вони підсвічуються червоним.; |-
| event_type
| varchar
| Тип події.; | style="background:#c8e6c9;" | Норма
|-
| Відхилено
| користувач системи відмовився.; timeout_seconds: int = 30

</syntaxhighlight>
}

я хочу натиснути кнопку «Підписати через Дія.Підпис», 
<syntaxhighlight lang="json">

== 33.; Відкриті питання ==
!; Коментар

{| class="wikitable"
!; | style="background:#f3e5f5;" | Контроль
|}

 request.status = "SIGN_ERROR"

!; |-
| style="background:#bbdefb;" | Блакитний
| #bbdefb
| операційна дія виконується.; |-
| Створення сесії Дія
| diia_session_id, статус, expires_at.; |-
| document_name
| varchar
| Назва документа.; '''Критично істотно:''' платформа не повинна вважати документ підписаним тільки після відкриття QR-коду або переходу в застосунок Дія.; new_status="MANUAL_REVIEW",

* масове підписання великого пакета документів;
* складний UI документообігу;
* власний кваліфікований надавач електронних довірчих послуг;
* повна юридична експертиза документів;
* інтеграційні функціональні можливості з усіма зовнішніми ЕДО-системами;
* автоматичне виправлення документів;
* архів довгострокового зберігання за окремими регламентами.; | Статус VERIFY_ERROR.; |-
| AC-13
| Підписант не відповідає очікуваному.; Дія
 document = document_repository.get_by_external_id(
 signature_session = signature_session_repository.get_by_diia_session_id(
== 5.; Основні сценарії інтеграції ==
|-
| Документів за день
| 184
| style="background:#e3f2fd;" | відомості
|-
| Очікують підпису
| 32
| style="background:#fff9c4;" | Увага
|-
| Підписано
| 128
| style="background:#c8e6c9;" | Норма
|-
| Перевірено
| 126
| style="background:#c8e6c9;" | Норма
|-
| Відхилено
| 8
| style="background:#ffcc80;" | Потрібна дія
|-
| Прострочено
| 10
| style="background:#ffcc80;" | Потрібна дія
|-
| Помилки callback
| 3
| style="background:#ef9a9a;" | Критично
|-
| Ручна перевірка
| 2
| style="background:#f3e5f5;" | Контроль
|}

 return existing

 "phone": "+380671112233",
!; | style="background:#f3e5f5;" | Фіолетовий
|}

 payload={"diia_session_id": response.session_id},

 entity_id=session.id,

 if result.code == "VALID":

 },
 request.status = "WAITING_SIGNATURE"
 event_type="SIGNATURE_VERIFY_EXCEPTION",
|-
| external_document_id
| ID документа в K2 ERP.; |-
| file_type
| varchar
| signature, signed_container, signed_pdf.; |}

!; Як зменшити

</div>

 "signer": {
=== 14.6.; Отримання статусу заявки ===
Retry дозволений для:

я хочу бачити статус підписання документа, 

== 29. Acceptance Criteria ==
</syntaxhighlight>
Як користувач системи, 

DIIA_SIGNATURE_TIMEOUT_SECONDS=30

документа забезпечується через Дія.Підпис має змогу використовуватись не тільки; додатково реалізовано а й для авторизації або підтвердження дії.; | Статус EXPIRED, дозволити створити нову.; |-
| Документ змінено після заявки
| Можна підписати неактуальну версію.; |-
| document_type
| varchar
| CONTRACT, ACT, APPLICATION тощо.; |}

!; |-
| AC-10
| Сесія прострочена.; Де застосовується

!; характеристика

<pre>
== 32.; Ризики ==
 existing = signature_request_repository.get_by_idempotency_key(
async def diia_signature_callback(request: Request):
|-
| id
| uuid
| ID заявки.; | Статус стає MANUAL_REVIEW або VERIFY_ERROR.; |-
| deep_link
| text
| Deep link.; Документ

!; | style="background:#fff9c4;" | Жовтий
|-
| Завершена
| COMPLETED
| Сесія завершена успішно.; |-
| CallbackValidationError
| Callback не пройшов перевірку.; |-
| Ручна перевірка
| хто перевірив, рішення для бізнесу, коментар.; |-
| VerificationError
| Підпис не пройшов перевірку.; 10.; |-
| Diia Client
| Python-клієнт для API Дії.; | style="background:#ef9a9a;" | Червоний
|-
| Ручна перевірка
| MANUAL_REVIEW
| Потрібна перевірка адміністратором.; Код

* створити FastAPI-проєкт;
* налаштувати PostgreSQL;
* створити моделі документів, заявок, сесій, підписів;
* налаштувати Alembic;
* реалізувати healthcheck.; # Чи потрібно перевіряти РНОКПП / ЄДРПОУ підписанта?;=== 5.1.; Підписання одного документа ===
 entity_type="signature_request",
|-
| style="background:#c8e6c9;" | Зелений
| #c8e6c9
| Успішно: підписано, перевірено, завершено.; |-
| created_by
| Хто створив версію.; Поле
{| class="wikitable"
=== 22.2. sign_documents ===
'''Критично істотно:''' callback повинен бути ідемпотентним.; |-
| old_status
| varchar
| Попередній статус.; користувач системи підтверджує підписання
{| class="wikitable"
</syntaxhighlight>
 |
 | 5.; !; !; |-
| Кінцева платформа
| K2 ERP / CRM / електронний документообіг / сайт / мобільний застосунок.; Що зберігати
== 35.; Див.; додатково ==
 diia_session_id=payload ["session_id"],
 entity_id=request.id,

== 4.; Передумови ==

{{DISPLAYTITLE:Технічне завдання: Накладення електронного підпису за допомогою Дія.Підпис у Python}}
=== 23.3. Callback controller ===
!; характеристика
 db.commit()
 v
</div>
 "k2_entity": "contract",
!; Компонент
!; |-
| Status Sync Service
| актуалізація статусів у K2 ERP.; event_type="DIIA_SIGNATURE_SESSION_CREATED",

!;=== 7.1.; користувач системи підписує документ ===
'''Критично істотно:''' Дія.Підпис не повинен підміняти внутрішню систему зберігання документів.; Очікуваний результат
 "document_type": "CONTRACT",
[[Категорія:Технічні завдання]]

DIIA_SIGNATURE_MAX_DOCUMENT_SIZE_MB=10

<pre>
7.; Поле

* реалізувати authenticate;
* реалізувати create_signature_session;
* реалізувати get_signature_session_status;
* реалізувати get_signature_result;
* реалізувати обробку помилок.; |-
| document_id
| uuid
| ID документа.; callback_processor.process_signature_result(

== 11.; технічна архітектура рішення для бізнесу ==

* договорів;
* актів виконаних робіт;
* рахунків;
* заяв;
* анкет;
* кадрових документів;
* первинних документів;
* документів ЕДО;
* документів K2 ERP;
* документів, які формуються в CRM;
* авторизації користувача через Дія.Підпис;
* підтвердження дії користувача в системі.; Тип

=== 14.3.; Створення документа ===
!; |-
| status
| varchar
| Статус документа.; |-
| status
| varchar
| Статус сесії.; |-
| diia_session_id
| varchar
| ID сесії в Дії.; |-
| AC-19
| є собою прострочені заявки.; | Dashboard, список документів, картка документа.; K2 ERP створює документ.; |-
| Dashboard API
| інформаційні дані для керівника та відповідальних осіб.; Тип

{| class="wikitable"
== 6.; Основні сутності ==
from datetime import datetime, timezone
!; Ризик
 request.document.status = "VERIFIED"
Документ вважається підписаним лише після отримання підтвердженого результату підписання.; Подія
</div>
=== 26.3.; Проблемні документи ===
!; * офіційно затверджений сервіс КЕП Дії.; Тип

=== 26.1.; Основні KPI ===

!; | платформа отримує callback і зберігає підпис.; Очікуваний результат
<pre>
 "expires_at": "2026-05-07T14:30:00+03:00"
=== 22.3. sign_document_versions ===
from fastapi import APIRouter, Request, HTTPException
"expires_at": response.expires_at,
document_file_id=document_version.file_id,
def check_connection(self) -> "ConnectionStatus":

12.1.; Призначення

23.5.; Перевірка підпису

db=db,
}

12.2.; Основні методи

request = signature_request_repository.get_by_id(db, signature_request_id)
; Колір
payload={
)
payload = await request.json()

платформа: POST /api/v1/diia-signature/integrations

14.7.; Callback від Дії

callback_event.status = "UNKNOWN_SESSION"