diff --git a/backend/alembic/versions/20250323_00edfb13e611-add_firmware_update_table.py b/backend/alembic/versions/20250323_00edfb13e611-add_firmware_update_table.py
new file mode 100644
index 0000000..39c447b
--- /dev/null
+++ b/backend/alembic/versions/20250323_00edfb13e611-add_firmware_update_table.py
@@ -0,0 +1,50 @@
+"""Add firmware_update table
+
+Revision ID: 00edfb13e611
+Revises: c7f72154c90b
+Create Date: 2025-03-23 14:01:14.029527+00:00
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = '00edfb13e611'
+down_revision: Union[str, None] = 'c7f72154c90b'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table('firmware_updates',
+    sa.Column('id', sa.Uuid(), nullable=False),
+    sa.Column('request_id', sa.Integer(), nullable=True),
+    sa.Column('status', sa.Enum('CREATED', 'SUBMITTED', 'DOWNLOADED', 'DOWNLOAD_FAILED', 'DOWNLOADING', 'DOWNLOAD_SCHEDULED', 'DOWNLOAD_PAUSED', 'IDLE', 'INSTALLATION_FAILED', 'INSTALLING', 'INSTALLED', 'INSTALL_REBOOTING', 'INSTALL_SCHEDULED', 'INSTALL_VERIFICATION_FAILED', 'INVALID_SIGNATURE', 'SIGNATURE_VERIFIED', name='firmwareupdatestatus'), nullable=True),
+    sa.Column('retries', sa.Integer(), nullable=True),
+    sa.Column('retry_interval', sa.Integer(), nullable=True),
+    sa.Column('location', sa.String(), nullable=True),
+    sa.Column('retrieve_date_time', sa.DateTime(), nullable=True),
+    sa.Column('install_date_time', sa.DateTime(), nullable=True),
+    sa.Column('chargepoint_id', sa.Uuid(), nullable=True),
+    sa.ForeignKeyConstraint(['chargepoint_id'], ['chargepoints.id'], ),
+    sa.PrimaryKeyConstraint('id')
+    )
+    op.create_index(op.f('ix_firmware_updates_chargepoint_id'), 'firmware_updates', ['chargepoint_id'], unique=False)
+    op.alter_column('users', 'is_active',
+               existing_type=sa.BOOLEAN(),
+               nullable=False)
+    # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.alter_column('users', 'is_active',
+               existing_type=sa.BOOLEAN(),
+               nullable=True)
+    op.drop_index(op.f('ix_firmware_updates_chargepoint_id'), table_name='firmware_updates')
+    op.drop_table('firmware_updates')
+    # ### end Alembic commands ###
diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py
index 26e5c03..f7b338d 100644
--- a/backend/app/models/__init__.py
+++ b/backend/app/models/__init__.py
@@ -2,6 +2,7 @@ __all__ = [
     "chargepoint_variable",
     "chargepoint",
     "connector",
+    "firmware_update",
     "id_token",
     "meter_value",
     "session",
diff --git a/backend/app/models/firmware_update.py b/backend/app/models/firmware_update.py
new file mode 100644
index 0000000..96d4f94
--- /dev/null
+++ b/backend/app/models/firmware_update.py
@@ -0,0 +1,20 @@
+import uuid
+from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String, Uuid
+
+from app.database import Base
+from app.schemas.firmware_update import FirmwareUpdateStatus
+
+class FirmwareUpdate(Base):
+    __tablename__ = "firmware_updates"
+
+    id = Column(Uuid, primary_key=True, default=uuid.uuid4)
+    request_id = Column(Integer)
+    status = Column(Enum(FirmwareUpdateStatus))
+
+    retries = Column(Integer)
+    retry_interval = Column(Integer)
+    location = Column(String)
+    retrieve_date_time = Column(DateTime)
+    install_date_time = Column(DateTime, nullable=True)
+
+    chargepoint_id = Column(Uuid, ForeignKey("chargepoints.id"), index=True)
diff --git a/backend/app/models/session.py b/backend/app/models/session.py
index b1c2213..cd0c5ba 100644
--- a/backend/app/models/session.py
+++ b/backend/app/models/session.py
@@ -8,7 +8,7 @@ class Session(Base):
 
     id = Column(Uuid, primary_key=True, default=uuid.uuid4)
     name = Column(String)
-    refresh_token = Column(String, unique=True, index=True)
+    refresh_token = Column(String, nullable=False, unique=True, index=True)
     last_used = Column(DateTime(timezone=True))
 
-    user_id = Column(Uuid, ForeignKey("users.id"), index=True)
\ No newline at end of file
+    user_id = Column(Uuid, ForeignKey("users.id"), nullable=False, index=True)
\ No newline at end of file
diff --git a/backend/app/ocpp_proto/chargepoint.py b/backend/app/ocpp_proto/chargepoint.py
index 88550a0..f9b8b48 100644
--- a/backend/app/ocpp_proto/chargepoint.py
+++ b/backend/app/ocpp_proto/chargepoint.py
@@ -8,6 +8,7 @@ from ocpp.v201.enums import Action, RegistrationStatusEnumType, TransactionEvent
 from ocpp.v201.call import GetBaseReport
 
 from app.services import (
+    firmware_service,
     variable_service,
     id_token_service,
     chargepoint_service,
@@ -108,6 +109,11 @@ class ChargePoint(cp):
             return call_result.TransactionEvent()
         else:
             return call_result.TransactionEvent(id_token_info=id_token_info)
+        
+    @on(Action.firmware_status_notification)
+    async def on_firmware_status_notification(self, status, request_id, **kwargs):
+        await firmware_service.update_firmware_status(self.id, request_id, status)
+        return call_result.FirmwareStatusNotification()
     
     @on(Action.meter_values)
     async def on_meter_values(self, **kwargs):
diff --git a/backend/app/routers/chargepoint_v1.py b/backend/app/routers/chargepoint_v1.py
index ff22bfd..b96e29d 100644
--- a/backend/app/routers/chargepoint_v1.py
+++ b/backend/app/routers/chargepoint_v1.py
@@ -28,10 +28,13 @@ from app.schemas.chargepoint_variable import (
     MutabilityType,
     SetVariableStatusType
 )
+from app.schemas.firmware_update import FirmwareUpdate, FirmwareUpdateCreate, FirmwareUpdateSubmissionResponse
 from app.models.chargepoint import ChargePoint as DbChargePoint
 from app.models.user import User as DbUser
 from app.models.chargepoint_variable import ChargepointVariable as DbChargepointVariable
+from app.models.firmware_update import FirmwareUpdate as DbFirmwareUpdate
 from app.security.jwt_bearer import JWTBearer
+from app.services import firmware_service
 
 router = APIRouter(
     prefix="/chargepoints",
@@ -293,3 +296,67 @@ async def update_chargepoint_variable(
         return ChargepointVariableResponse(status=status)
     except TimeoutError:
         raise HTTPException(status_code=503, detail="Chargepoint didn't respond in time.")
+    
+@router.get(path="/{chargepoint_id}/firmware-updates", response_model=list[FirmwareUpdate])
+async def get_firmware_updates(
+    chargepoint_id: UUID,
+    db: Session = Depends(get_db),
+    token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
+):
+    chargepoint = db.get(DbChargePoint, chargepoint_id)
+    if chargepoint is None:
+        raise HTTPException(status_code=404, detail="Chargepoint not found")
+    
+    firmware_updates = db.query(DbFirmwareUpdate).filter(
+        DbFirmwareUpdate.chargepoint_id == chargepoint_id
+    ).all()
+    
+    return firmware_updates
+
+@router.get(path="/{chargepoint_id}/firmware-updates/{firmware_update_id}", response_model=FirmwareUpdate)
+async def get_firmware_update(
+    chargepoint_id: UUID,
+    firmware_update_id: UUID,
+    db: Session = Depends(get_db),
+    token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
+):
+    chargepoint = db.get(DbChargePoint, chargepoint_id)
+    if chargepoint is None:
+        raise HTTPException(status_code=404, detail="Chargepoint not found")
+    
+    firmware_update = db.query(DbFirmwareUpdate).filter(
+        DbFirmwareUpdate.chargepoint_id == chargepoint_id,
+        DbFirmwareUpdate.id == firmware_update_id
+    ).first()
+    if firmware_update is None:
+        raise HTTPException(status_code=404, detail="FirmwareUpdate not found")
+    
+    return firmware_update
+
+@router.post(path="/{chargepoint_id}/firmware-updates", response_model=FirmwareUpdate)
+async def create_firmware_update(
+    chargepoint_id: UUID,
+    firmware_update: FirmwareUpdateCreate,
+    db: Session = Depends(get_db),
+    token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
+):
+    chargepoint = db.get(DbChargePoint, chargepoint_id)
+    if chargepoint is None:
+        raise HTTPException(status_code=404, detail="Chargepoint not found")
+    
+    firmware_update = await firmware_service.create_firmware_update(chargepoint_id, firmware_update)
+    return firmware_update
+
+@router.post(path="/{chargepoint_id}/firmware-updates/{firmware_update_id}/submit", response_model=ChargePointResetResponse)
+async def submit_firmware_update(
+    chargepoint_id: UUID,
+    firmware_update_id: UUID,
+    token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
+):
+    if chargepoint_manager.is_connected(chargepoint_id) == False:
+        raise HTTPException(status_code=503, detail="Chargepoint not connected.")
+    try:
+        firmware_update, status = await firmware_service.submit_firmware_update(firmware_update_id)
+        return FirmwareUpdateSubmissionResponse(firmware_update, status)
+    except TimeoutError:
+        raise HTTPException(status_code=503, detail="Chargepoint didn't respond in time.")
diff --git a/backend/app/schemas/firmware_update.py b/backend/app/schemas/firmware_update.py
new file mode 100644
index 0000000..9436810
--- /dev/null
+++ b/backend/app/schemas/firmware_update.py
@@ -0,0 +1,42 @@
+from datetime import datetime
+import enum
+from typing import Optional
+from uuid import UUID
+from pydantic import BaseModel
+
+class FirmwareUpdateStatus(enum.Enum):
+    CREATED = "xCreated"
+    SUBMITTED = "xSubmitted"
+    DOWNLOADED = "Downloaded"
+    DOWNLOAD_FAILED = "DownloadFailed"
+    DOWNLOADING = "Downloading"
+    DOWNLOAD_SCHEDULED = "DownloadScheduled"
+    DOWNLOAD_PAUSED = "DownloadPaused"
+    IDLE = "Idle"
+    INSTALLATION_FAILED = "InstallationFailed"
+    INSTALLING = "Installing"
+    INSTALLED = "Installed"
+    INSTALL_REBOOTING = "InstallRebooting"
+    INSTALL_SCHEDULED = "InstallScheduled"
+    INSTALL_VERIFICATION_FAILED = "InstallVerificationFailed"
+    INVALID_SIGNATURE = "InvalidSignature"
+    SIGNATURE_VERIFIED = "SignatureVerified"
+
+class FirmwareUpdateBase(BaseModel):
+    retries: int
+    retry_interval: int
+    location: str
+    retrieve_date_time: datetime
+    install_date_time: Optional[datetime]
+
+class FirmwareUpdate(FirmwareUpdateBase):
+    id: UUID
+    request_id: int
+    status: FirmwareUpdateStatus
+
+class FirmwareUpdateCreate(FirmwareUpdateBase):
+    pass
+
+class FirmwareUpdateSubmissionResponse(BaseModel):
+    firmware_update: FirmwareUpdate
+    status: str
diff --git a/backend/app/services/firmware_service.py b/backend/app/services/firmware_service.py
new file mode 100644
index 0000000..e849cda
--- /dev/null
+++ b/backend/app/services/firmware_service.py
@@ -0,0 +1,62 @@
+from uuid import UUID
+
+from ocpp.v201.call import UpdateFirmware
+from ocpp.v201.call_result import UpdateFirmware as UpdateFirmwareResult
+from ocpp.v201.datatypes import FirmwareType
+
+from app.database import SessionLocal
+from app.models.chargepoint import ChargePoint
+from app.models.firmware_update import FirmwareUpdate
+from app.ocpp_proto import chargepoint_manager
+from app.schemas.firmware_update import FirmwareUpdateCreate, FirmwareUpdateStatus
+
+async def create_firmware_update(chargepoint_id: UUID, firmware_update: FirmwareUpdateCreate) -> FirmwareUpdate:
+    with SessionLocal() as db:
+        db_chargepoint = db.get(ChargePoint, chargepoint_id)
+        latest_firmware_update = db.query(FirmwareUpdate).filter(FirmwareUpdate.chargepoint_id == db_chargepoint.id).order_by(FirmwareUpdate.request_id.desc()).first()
+        new_request_id = latest_firmware_update.request_id + 1 if latest_firmware_update else 1
+        db_firmware_update = FirmwareUpdate(
+            request_id=new_request_id,
+            status=FirmwareUpdateStatus.CREATED,
+            retries=firmware_update.retries,
+            retry_interval=firmware_update.retry_interval,
+            location=firmware_update.location,
+            retrieve_date_time=firmware_update.retrieve_date_time,
+            install_date_time=firmware_update.install_date_time,
+            chargepoint_id=db_chargepoint.id
+        )
+        db.add(db_firmware_update)
+        db.commit()
+        db.refresh(db_firmware_update)
+        return db_firmware_update
+
+async def submit_firmware_update(firmware_update_id: UUID) -> tuple[FirmwareUpdate, str]:
+    with SessionLocal() as db:
+        db_firmware_update = db.get(FirmwareUpdate, firmware_update_id)
+        try:
+            result: UpdateFirmwareResult = await chargepoint_manager.call(
+                db_firmware_update.chargepoint_id,
+                payload=UpdateFirmware(
+                    request_id=db_firmware_update.request_id,
+                    retries=db_firmware_update.retries,
+                    retry_interval=db_firmware_update.retry_interval,
+                    firmware=FirmwareType(
+                        location=db_firmware_update.location,
+                        retrieve_date_time=db_firmware_update.retrieve_date_time,
+                        install_date_time=db_firmware_update.install_date_time
+                    )
+                ))
+            if result.status == "Accepted" or result.status == "AcceptedCanceled":
+                db_firmware_update.status = FirmwareUpdateStatus.SUBMITTED
+                db.commit()
+
+            return db_firmware_update, result.status
+        except TimeoutError as e:
+            raise e
+        
+async def update_firmware_status(chargepoint_identity: str, request_id: int, status: FirmwareUpdateStatus):
+    with SessionLocal() as db:
+        db_chargepoint = db.query(ChargePoint).filter(ChargePoint.identity == chargepoint_identity).first()
+        db_firmware_update = db.query(FirmwareUpdate).filter(FirmwareUpdate.chargepoint_id == db_chargepoint.id).filter(FirmwareUpdate.request_id == request_id).first()
+        db_firmware_update.status = status
+        db.commit()