diff --git a/index.pl b/index.pl
index 2df0048fda83dc0cf349922472412c5f889b8c8c..0f5622ba23f15b3568f6e195fc19c7e67bb149b1 100755
--- a/index.pl
+++ b/index.pl
@@ -33,11 +33,13 @@ my $cache_iris_rt = Cache::File->new(
 my $dbname = $ENV{TRAVELYNX_DB_FILE} // 'travelynx.sqlite';
 
 my %action_type = (
-	checkin  => 1,
-	checkout => 2,
-	undo     => 3,
+	checkin        => 1,
+	checkout       => 2,
+	undo           => 3,
+	cancelled_from => 4,
+	cancelled_to   => 5,
 );
-my @action_types = (qw(checkin checkout undo));
+my @action_types = (qw(checkin checkout undo cancelled_from cancelled_to));
 my %token_type   = (
 	status  => 1,
 	history => 2,
@@ -476,7 +478,9 @@ sub get_station {
 }
 
 helper 'checkin' => sub {
-	my ( $self, $station, $train_id ) = @_;
+	my ( $self, $station, $train_id, $action_id ) = @_;
+
+	$action_id //= $action_type{checkin};
 
 	my $status = get_departures($station);
 	if ( $status->{errstr} ) {
@@ -504,10 +508,17 @@ helper 'checkin' => sub {
              # XXX same workaround: We can't checkin immediately after checkout.
 				sleep(1);
 			}
+			elsif ( $user->{cancelled} ) {
+
+				# Same
+				sleep(1);
+				$self->cancelled_to($station);
+				sleep(1);
+			}
 
 			my $success = $self->app->action_query->execute(
 				$self->current_user->{id},
-				$action_type{checkin},
+				$action_id,
 				$self->get_station_id(
 					ds100 => $status->{station_ds100},
 					name  => $status->{station_name}
@@ -563,13 +574,15 @@ helper 'undo' => sub {
 };
 
 helper 'checkout' => sub {
-	my ( $self, $station, $force ) = @_;
+	my ( $self, $station, $force, $action_id ) = @_;
+
+	$action_id //= $action_type{checkout};
 
 	my $status   = get_departures( $station, 180 );
 	my $user     = $self->get_user_status;
 	my $train_id = $user->{train_id};
 
-	if ( not $user->{checked_in} ) {
+	if ( not $user->{checked_in} and not $user->{cancelled} ) {
 		return 'You are not checked into any train';
 	}
 	if ( $status->{errstr} and not $force ) {
@@ -582,7 +595,7 @@ helper 'checkout' => sub {
 		if ($force) {
 			my $success = $self->app->action_query->execute(
 				$self->current_user->{id},
-				$action_type{checkout},
+				$action_id,
 				$self->get_station_id(
 					ds100 => $status->{station_ds100},
 					name  => $status->{station_name}
@@ -892,6 +905,7 @@ helper 'get_user_status' => sub {
 		}
 		return {
 			checked_in      => ( $cols[0] == $action_type{checkin} ),
+			cancelled       => ( $cols[0] == $action_type{cancelled_from} ),
 			timestamp       => $action_ts,
 			timestamp_delta => $now->epoch - $action_ts->epoch,
 			sched_ts        => $sched_ts,
@@ -1070,8 +1084,11 @@ get '/api/v0/:action/:token' => sub {
 		$self->render(
 			json => {
 				deprecated => \0,
-				checked_in => $status->{checked_in} ? \1 : \0,
-				station    => {
+				checked_in => (
+					     $status->{checked_in}
+					  or $status->{cancelled}
+				) ? \1 : \0,
+				station => {
 					ds100     => $status->{station_ds100},
 					name      => $status->{station_name},
 					uic       => $station_eva,
@@ -1302,7 +1319,7 @@ post '/action' => sub {
 	if ( $params->{action} eq 'checkin' ) {
 
 		my ( $train, $error )
-		  = $self->checkin( $params->{station}, $params->{train}, );
+		  = $self->checkin( $params->{station}, $params->{train} );
 
 		if ($error) {
 			$self->render(
@@ -1321,7 +1338,7 @@ post '/action' => sub {
 		}
 	}
 	elsif ( $params->{action} eq 'checkout' ) {
-		my $error = $self->checkout( $params->{station}, $params->{force}, );
+		my $error = $self->checkout( $params->{station}, $params->{force} );
 
 		if ($error) {
 			$self->render(
@@ -1357,6 +1374,47 @@ post '/action' => sub {
 			);
 		}
 	}
+	elsif ( $params->{action} eq 'cancelled_from' ) {
+		my ( undef, $error )
+		  = $self->checkin( $params->{station}, $params->{train},
+			$action_type{cancelled_from} );
+
+		if ($error) {
+			$self->render(
+				json => {
+					success => 0,
+					error   => $error,
+				},
+			);
+		}
+		else {
+			$self->render(
+				json => {
+					success => 1,
+				},
+			);
+		}
+	}
+	elsif ( $params->{action} eq 'cancelled_to' ) {
+		my $error = $self->checkout( $params->{station}, 1,
+			$action_type{cancelled_to} );
+
+		if ($error) {
+			$self->render(
+				json => {
+					success => 0,
+					error   => $error,
+				},
+			);
+		}
+		else {
+			$self->render(
+				json => {
+					success => 1,
+				},
+			);
+		}
+	}
 	else {
 		$self->render(
 			json => {
diff --git a/public/static/css/local.css b/public/static/css/local.css
index 7502b647a4c48a6a1a45b856a61f0adae7b86d81..68f9ab12747ef8c5f3890f066ed671f0bd3dfae2 100644
--- a/public/static/css/local.css
+++ b/public/static/css/local.css
@@ -1,6 +1,8 @@
 .action-checkin,
 .action-checkout,
-.action-undo {
+.action-undo,
+.action-cancelled-from,
+.action-cancelled-to {
 	cursor: pointer;
 }
 
diff --git a/public/static/js/travelynx-actions.js b/public/static/js/travelynx-actions.js
index 0a3d0925f61e7060ccea8a1af309270fd555bf6a..d47df7761ebb0841af814fd6a8cef6a79716e41e 100644
--- a/public/static/js/travelynx-actions.js
+++ b/public/static/js/travelynx-actions.js
@@ -44,6 +44,24 @@ $(document).ready(function() {
 		var req = {
 			action: 'undo',
 		};
-		tvly_run(link, req, window.location.href);
+		tvly_run(link, req, '/');
+	});
+	$('.action-cancelled-from').click(function() {
+		var link = $(this);
+		var req = {
+			action: 'cancelled_from',
+			station: link.data('station'),
+			train: link.data('train'),
+		};
+		tvly_run(link, req, '/');
+	});
+	$('.action-cancelled-to').click(function() {
+		var link = $(this);
+		var req = {
+			action: 'cancelled_to',
+			station: link.data('station'),
+			force: true,
+		};
+		tvly_run(link, req, '/');
 	});
 });
diff --git a/public/static/js/travelynx-actions.min.js b/public/static/js/travelynx-actions.min.js
index 9c37d8ddf3c8d9f2807335f3c3fc86856e8bd386..4fe0e765ee4efc4e33d013a0cefa98d001512ea8 100644
--- a/public/static/js/travelynx-actions.min.js
+++ b/public/static/js/travelynx-actions.min.js
@@ -1 +1 @@
-function tvly_run(n,t,i,a){var c='<i class="material-icons">error</i>',o=$('<div class="progress"><div class="indeterminate"></div></div>');n.hide(),n.after(o),$.post("/action",t,function(t){t.success?$(location).attr("href",i):(M.toast({html:c+" "+t.error}),o.remove(),a&&a(),n.append(" "+c),n.show())})}$(document).ready(function(){$(".action-checkin").click(function(){var t=$(this);tvly_run(t,{action:"checkin",station:t.data("station"),train:t.data("train")},"/")}),$(".action-checkout").click(function(){var t=$(this),n={action:"checkout",station:t.data("station"),force:t.data("force")};tvly_run(t,n,"/s/"+n.station,function(){t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0)})}),$(".action-undo").click(function(){tvly_run($(this),{action:"undo"},window.location.href)})});
+function tvly_run(n,t,a,c){var i='<i class="material-icons">error</i>',o=$('<div class="progress"><div class="indeterminate"></div></div>');n.hide(),n.after(o),$.post("/action",t,function(t){t.success?$(location).attr("href",a):(M.toast({html:i+" "+t.error}),o.remove(),c&&c(),n.append(" "+i),n.show())})}$(document).ready(function(){$(".action-checkin").click(function(){var t=$(this);tvly_run(t,{action:"checkin",station:t.data("station"),train:t.data("train")},"/")}),$(".action-checkout").click(function(){var t=$(this),n={action:"checkout",station:t.data("station"),force:t.data("force")};tvly_run(t,n,"/s/"+n.station,function(){t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0)})}),$(".action-undo").click(function(){tvly_run($(this),{action:"undo"},"/")}),$(".action-cancelled-from").click(function(){var t=$(this);tvly_run(t,{action:"cancelled_from",station:t.data("station"),train:t.data("train")},"/")}),$(".action-cancelled-to").click(function(){var t=$(this);tvly_run(t,{action:"cancelled_to",station:t.data("station"),force:!0},"/")})});
diff --git a/templates/departures.html.ep b/templates/departures.html.ep
index ee87496d89afa939e864ffad051270274a334629..47c487214e4ab38d6166a459732d1cdccae8650c 100644
--- a/templates/departures.html.ep
+++ b/templates/departures.html.ep
@@ -52,22 +52,24 @@
 			</thead>
 			<tbody>
 				% for my $result (@{$results}) {
-					% my $class = "";
+					% my $td_class = '';
+					% my $link_class = 'action-checkin';
 					% if ($result->departure_is_cancelled) {
-						% $class = "cancelled";
+						% $td_class = "cancelled";
+						% $link_class = 'action-cancelled-from';
 					% }
 					<tr>
 						<td>
-							<a class="action-checkin" data-station="<%= $ds100 %>" data-train="<%= $result->train_id %>">
+							<a class="<%= $link_class %>" data-station="<%= $ds100 %>" data-train="<%= $result->train_id %>">
 								<%= $result->line %>
 							</a>
 						</td>
-						<td class="<%= $class %>">
-							<a class="action-checkin" data-station="<%= $ds100 %>" data-train="<%= $result->train_id %>">
+						<td class="<%= $td_class %>">
+							<a class="<%= $link_class %>" data-station="<%= $ds100 %>" data-train="<%= $result->train_id %>">
 								<%= $result->destination %>
 							</a>
 						</td>
-						<td class="<%= $class %>"><%= $result->departure->strftime('%H:%M') %>
+						<td class="<%= $td_class %>"><%= $result->departure->strftime('%H:%M') %>
 							% if ($result->departure_delay) {
 								(+<%= $result->departure_delay %>)
 							% }
diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep
index dcc0083b4a5027e7cd746eb60508f6465e1da6e4..83ad2a1cc841717c40444e77bdf21d9eb9dbf6c2 100644
--- a/templates/landingpage.html.ep
+++ b/templates/landingpage.html.ep
@@ -37,6 +37,33 @@
 					</div>
 				</div>
 			% }
+			% elsif ($status->{cancelled}) {
+				<div class="card yellow lighten-4">
+					<div class="card-content">
+						<span class="card-title">Zugausfall dokumentieren</span>
+						<p>Prinzipiell wärest du nun eingecheckt in
+							<%= $status->{train_type} %> <%= $status->{train_no} %>
+							ab <%= $status->{station_name} %>, doch dieser Zug fällt aus.
+							% if ($status->{timestamp_delta} < 3600) {
+								<a class="action-undo"><i class="material-icons">undo</i> Checkinversuch rückgängig</a>
+							% }
+							</p>
+						<p>Falls du den Zugausfall z.B. für ein Fahrgastrechteformular
+							dokumentieren möchtest, wähle bitte jetzt deine geplante
+							Zielstation aus. Achtung: Momentan wird dabei keine
+							Soll-Ankunftszeit gespeichert, das zu beheben steht auf
+							der Todoliste.</p>
+						<table>
+							<tbody>
+								% my $is_after = 0;
+								% for my $station (@{$status->{route_after}}) {
+									<tr><td><a class="action-cancelled-to" data-station="<%= $station %>"><%= $station %></a></td></tr>
+								% }
+							</tbody>
+						</table>
+					</div>
+				</div>
+			% }
 			% else {
 				<div class="card grey darken-4">
 					<div class="card-content white-text">
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index 8f5bdc241a14495d4023034da9017fc990abfa08..2589788b473f55d39e6e43b443f31ec02c1f20d3 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -5,7 +5,7 @@
 	<meta charset="utf-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 	<meta name="theme-color" content="#673ab7">
-	% my $av = 'v3'; # asset version
+	% my $av = 'v4'; # asset version
 	%= stylesheet "/static/${av}/css/materialize.min.css"
 	%= stylesheet "/static/${av}/css/material-icons.css"
 	%= stylesheet "/static/${av}/css/local.css"