Webhooks Integration 🌐
Introduction
If Webhooks
are enabled in the JobTemplate
's configuration. AIDA publishes notifications when relevant events occur during
issuance.
Webhooks
are JSON Encoded
and transmitted as HTTP POST
quests.
For operations like Chip Encoding, Readback and OCR webhooks are mandatory. The receiving application must respond to webhook requests
with a successful HTTP Status Code (2xx)
We can separate messages in 3 categories:
External Process Messages
These messages indicate that AIDA suspended the execution of a Job and it is waiting an external operation to complete (OCR output validation, Chip Encoding, Magnetic / Chip / OCR readback). The receiving application can singnal the completion of the operation by sending an ExternalProcessCompletedMessage to the SignalExternalProcessCompleted API endpoint.
Examples
Recoverable Error Messages
These messages indicate that issuance was suspended and manual intervention is required.
Recoverable error statuses are:
- Input feeder empty
- Open Interlock
- Card Jam (Only in box systems)
The controlling application can:
- Tell AIDA to resume issuance and retry
- Stop issuance
Examples
Diagnostic Messages
All other messages require the receiving application to respond with a 2xx
status code, indicating that the message was received.
- WebhooksReceiverHealthCheck
- WorkflowSchedulerStarted
- WorkflowSchedulerStopped
- WorkflowStarted
- WorkflowCanceled
- WorkflowCompleted
- WorkflowFaulted
Error Codes & Job Statuses
Sequence Diagrams
Examples
Serialization
Enums
are serialized as strings, and properties are serialized in camelCase
.
Webhook Messages Types
Below there's a listing of all webhook messages.
MessageType
public enum MessageType
{
// default value, webhook receivers should never get this
Unknown,
// encdoer loaded message (sent when a card is placed in one of the encoders)
EncoderLoaded,
// ocr executed message (sent at the end of an ocr reading)
OcrExecuted,
// workflow started (sent when a new job is started)
WorkflowStarted,
// workflow completed (sent when a job is completed)
WorkflowCompleted,
// workflow faulted (sent when a card is rejected)
WorkflowFaulted,
// workflow cancelled (sent when a job is cancelled)
WorkflowCancelled,
// -- deprecated --
// The feeder empty message was replaced by WorkflowSchedulerSuspended
// with error code = FeederEmpty
FeederEmpty,
// workflwo scheduler started message (if webhooks are enabled, it is sent when
// AIDA beings polling the database for jobs to process)
WorkflowSchedulerStarted,
// workflow scheduler stopped message (issuance stopped)
WorkflowSchedulerStopped,
// workflow scheduler suspended (issuance suspedned)
WorkflowSchedulerSuspended,
// healtcheck message
HealthCheck
}
Common Properties
All webhook messages inherit from WorkflowMessage
which has the following properties
// All webhook messages extends from WorkflowMessage
abstract class WorkflowMessage
{
// identifiers to recongnize which machine is sending the message
// these are usefull if the receiving application control more than one
// machine
public string MachineName;
public string MachineSerial;
public string HostName;
// The value of the job_id field in the Data Exchange Table
public string JobId;
// The value ofr the batch_id field
public string BatchId;
// The value ofr the correlation_id field
public string CorrelationId;
// unique identifier for the webhook message
public string MessageId;
// identifier of the process for this job. These field is required to resume jobs
// when using the Signal
public string WorkflowInstanceId;
// the name of the workflow definition used for the job's workflow instance
public string WorkflowInstanceName;
// Enum that identifies the message kind
public MessageType MessageType;
// The current status of the job
public JobStatus JobStatus;
// An error code that signals the reason why the job has the current JobStatus
public JobErrorCodes ErrorCode;
// The same value as the `AdditionalMetadata` value sent to AIDA when starting
// issuance. See `StartWorkflowScheduler` API endpoint documentation for more
// information
public Dictionary<string, object?> AdditionalMetadata;
// If `true` all perso operations where executed on the card.
// This is usefull if all operations where executed but the workflow fails.
// Example:
// Job template with `Chip Encoding` and `Laser Engraving` enabled.
// - Chip Encoding executes without problems
// - Laser Engraving executes without problems
// - The crad gets stuck in the machine when AIDA is trying to move it from the
// laser position to the output stacker.
// In this case the job will enter the `Faulted` state with error code `CardJam` and
// AIDA will publish the `WorkflowFaultedMessage` with `ErrorCode` = `CardJam` and
// `DocumentProduced` = True
public bool DocumentProduced;
// Set to `true` when any of the operations executed on the card are destructive:
// - Laser Engraving
// - Chip Encoding
// - Magnetic stripe write
public bool DestructiveOperationExecuted;
// If a critical error occurs (CardJam is one of these), AIDA will cancel the execution
// of jobs that cannot recover from the current error state.
// The SourceJobInstanceId contains the Identifier of the job that triggered the error
// in first place
public string SourceJobInstanceId;
// Contains the `CLR` type name and it is used to support polymorphism
// during serialization/deserialization
public string Discriminator;
}
Job Statuses and Error Codes
Job statuses are used to indicate if the job is currently running or not. Error codes are used to signal the reason why Jobs
are in a specific JobStatus
;
JobStatus
public enum JobStatus
{
None,
/// <summary>
/// Inserted in the data exchange table but not yet picked up by the workflow scheduler
/// </summary>
Waiting,
/// <summary>
/// The workflow engine is about to executing (or is executing) the first
/// activity. we consider the job
/// Running once we know that the card was moved from the feeder.
/// </summary>
Starting,
/// <summary>
/// We are resuming the workflow after suspension.
/// This happens when we receive a notification from
/// a webhooks receiver. Workflows are suspended
/// when we need to wait for external systems to perform
/// operations on the document (chip encoding, OCR validation etc, feeder empty).
/// </summary>
Resuming,
/// <summary>
/// workflow created and started
/// </summary>
Running,
/// <summary>
/// workflow completed correctly
/// </summary>
Completed,
///<summary>
/// workflow was manually cancelled
/// </summary>
Cancelled,
/// <summary>
/// Workflow waiting for an external signal to resume
/// </summary>
Suspended,
/// <summary>
/// rejected
/// </summary>
Rejected,
/// <summary>
/// faulted
/// </summary>
Faulted,
}
ErrorCodes
public enum JobErrorCodes
{
/// <summary>
/// No errors so far
/// </summary>
NoErrors,
/// <summary>
/// Workflow scheduler stopped from API
/// </summary>
ManualStop,
/// <summary>
/// Generic Error
/// </summary>
GenericError,
/// <summary>
/// The firmware returned CardJam during a move card
/// </summary>
CardJam,
/// <summary>
/// SCAPS returned an error code during mark
/// </summary>
MarkLayoutFailed,
/// <summary>
/// Open interlocks
/// </summary>
OpenInterlock,
/// <summary>
/// The layout that is being marked lays outside the operable area of the laser
/// modules scan head
/// </summary>
MarkErrorLayoutOutOfBounds,
/// <summary>
/// The entity list that needs to be marked is empty
/// </summary>
MarkErrorEntitySelectionEmpty,
/// <summary>
/// Another mark process is being executed
/// </summary>
MarkErrorMarkAlreadyExecuting,
/// <summary>
/// External process signaled completion with outcome "Faulted"
/// </summary>
ChipPersonalizationFailed,
/// <summary>
/// Mag stripe encoder nack while reading
/// </summary>
MagStripeReadFailed,
/// <summary>
/// Mag stripe encoder nack while writing
/// </summary>
MagStripeWriteFailed,
/// <summary>
/// External validation signaled completion with outcome "Faulted"
/// </summary>
OcrUnexpectedResult,
/// <summary>
/// Image acquisition failed when trying to perform ocr
/// </summary>
OcrFailedToAcquireImage,
/// <summary>
/// OCR algorithm execution failed. This might happen if configuration or parameters
/// are invalid
/// </summary>
OcrFailedToExecuteAlgorithm,
/// <summary>
/// We could not find the configured pattern. This might indicate that the image
/// acquired is not good or the card is not in the correct position
/// </summary>
AutoPosFailedToMatchPattern,
/// <summary>
/// The webhook receiver did not send an HTTP Response within the configured timeout
/// </summary>
WebhookAckTimeout,
/// <summary>
/// Cannot reach the webhooks receiver
/// </summary>
WebhooksServerUnreachable,
/// <summary>
/// Input feeder is empty.
/// </summary>
FeederEmpty,
/// <summary>
/// During startup AIDA checks if there are workflows in inconsistent states
/// (eg. Running, Suspended).
/// If it finds any, it sets the status to `Cancelled` with this error code.
/// Workflows in inconsistent states if the machine is shutdown during issuance
/// </summary>
CancelledOnStartup,
/// <summary>
/// We received the ExternalProcessCompletedMessage
/// with value `Faulted` after `OcrValidation`
/// </summary>
OcrExternalValidationKo,
/// <summary>
/// Happens if the CCI Api returns 0 when loading the sjf file
/// </summary>
FailedToLoadJobTemplate,
/// <summary>
/// This status is set if a card needs to be moved but the transport is in error
/// state. Might happen in a situation like the following:
///
/// - card jam occurs while moving card `N`
/// - card `N+1` needs to be moved but the status is not in `Ready` state
/// </summary>
InvalidTransportStatus,
}
EncoderLoadedMessage (Suspends Workflow)
Sent by the machine when a card is placed in one of the available encoding stations.
/// Sent when the machine places a card in one of the encoding stations
public class EncoderLoadedMessage : WorkflowMessage
{
public override MessageType Type => MessageType.EncoderLoaded;
// String identifier for the encoder (set in AIDA server configuration)
public String EncoderId;
// Index of the encoder
// (0 based index for the encoder, indices increase from right to left)
public int EncoderIndex;
// URL that needs to be invoked to signal AIDA
// when the encoding process is finished
// You can ignore this parameter and just invoke the
// SignalExternalProcessCompleted API Endpoint
public String CallbackUrl;
}
OcrExecutedMessage (Suspends Workflow)
At the end of an OCR reading, the machine sends OcrExecutedMessage
containing the
result of the readings.
public class OcrExecutedMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.OcrExecuted;
// The result of the OCR inspections ran by the machine
public Array<RuntimeOcrInspectionResult> Results;
}
public class RuntimeOcrInspectionResult
{
// the name of the inspection used to execute the OCR reading
// this is part of the job template's configuration
public String InspectionName;
// The reuslt of the OCR Reading
public OCRResult OcrResult;
}
public class OCRResult
{
// UTF8 text returned by the OCR Engine
public String Text;
// Mean confidence of the recongnized text
// These can be used to decide if the r
public float MeanConfidence;
}
WorkflowSchedulerProcessSuspendedMessage (Suspends Workflow)
This message can be published for 2 reasons:
- The Input Feeder Is empty (
ErrorCode = FeederEmpty
) - The machine detected an open interlock before engraving the card. The before is very important here, if the machine detects open interlocks during laser engraving the card will be rejected, since opening an interlock automatically disables the laser source.
- A card jam occurred and can be recovered (
ErrorCode = CardJam
Only for BOX systems, desktop machines cannot recover fromCARD_JAM
)
public class WorkflowSchedulerProcessSuspendedMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.WorkflowSchedulerSuspended;
}
WebhookReceiverHealthCheckMessage
The WebhookReceiverHealthCheckMessage
is used by AIDA to test WebhooksReceiver
availability, it is sent:
- When users click the
Test
button in theWebapp
- During
Issuance graceful stop
public class WebhookReceiverHealthCheckMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.HealthCheck;
}
WorkflowCancelledMessage
Published when a workflow is cancelled. The reason why the job was cancelled is indicated in the ErrorCode
property.
public class WorkflowCancelledMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.WorkflowCancelled;
}
WorkflowCompletedMessage
Published at the end of the personalization process. This message indicates that the card was successfully produced and was moved to the output stacker
public class WorkflowCompletedMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.WorkflowCompleted;
}
WorkflowFaultedMessage
Published if an error occurred during the production process or any of the external processes returned Outcome = Faulted
.
The ErrorCode
property contains the reason of the fault.
public class WorkflowFaultedMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.WorkflowFaulted;
}
WorkflowSchedulerStartedMessage
Sent when AIDA starts polling the database for records
public class WorkflowSchedulerStartedMessage : WorkflowMessage
{
public MessageType MessageType => MessageType.WorkflowSchedulerStarted;
}
WorkflowSchedulerStoppedMessage
Sent when all running jobs finished executing. This message is necessary because issuance can be stopped in 2 modalities
- Graceful stop - AIDA stops processing records from the DB and finish all the processes that are already executing
- Abort - AIDA stops processing records from the DB, aborts all running operations, cancels all running jobs which will result in cards being rejected.
public class WorkflowSchedulerStoppedMessage : WorkflowMessage
{
public WorkflowSchedulerStopReason StopReason;
}
public enum WorkflowSchedulerStopReason
{
// The scheduler finished processing all the cards in a batch
BatchCompleted,
// The StopWorkflowScheduler endpoint was invoked
// while issuance was suspended with `FeederEmpty` error code
FeederEmpty,
// The StopWorkflowScheduler endpoint was invoked to stop issuance
ManualStop,
// A card got stuck in the machine's transport
// and was not possible to recover
CardJam,
// An uncaught exception killed a job
UnhandledException,
// The webhooks receiver is unreachable
WebhooksServerUnreachable,
// The data source configuration for the selected job template is invalid
InvalidDataSourceConfiguration,
// AIDA was not able to reset the transport when starting issuance
TransportResetFailed
}
ExternalProcessCompletedMessage
This is the payload that Webhook Receivers
must send when invoking the SignalExternalProcessCompleted
endpoint. Only 2 properties are required and they are:
WorkflowInstanceId
Outcome
AIDA includes the WorkflowInstanceId
in all webhook messages. Outcome
is required and tells AIDA if the external process succeeded or not.
set it to Completed
if the operation succeeds, Faulted
otherwise.
public class ExternalProcessCompletedMessage
{
// Optional. If present it is stored in the Data Exchange Table. Can be used
// to store error descriptions when the external process fails
public string? ErrorReason { get; set; }
// It must contain the same value sent by AIDA in webhook messages.
public string WorkflowInstanceId { get; set; }
// Optional. Overrides the default behavior of AIDA when it handles
// The ExternalProcessCompletedMessage.
public WorkflowAction? RequiredAction { get; set; }
// The outcomeo of the external operation
public ExternalProcessOutcome Outcome { get; set; }
}
// The outcome of the external process execution result
public enum ExternalProcessOutcome
{
// The external process succeeded
// The process resumes
Completed,
// The external process succeeded
// The card is rejected
Faulted,
// The external was cancelled
// The card is rejected
Canceled
}
// Used in ExternalProcessCompleted to override the default behavior
public enum WorkflowAction
{
// Forces AIDA to reject the card
Reject,
// Forces AIDA to resume the process
Resume
}
Sequence Diagrams
Below you can find sequence diagrams that illustrate the Issuance startup sequence and the interations between AIDA and the webhook receiver during job execution.
Issuance Startup Sequence
Chip Encoding Sequence
Examples
Handling EncoderLoaded Message
Once AIDA loads the encoding station it will send a request like the following
POST /ixla/aida/v1/webhooks HTTP/1.1
Host: webhooks-host:4567
Accept: application/json
Content-Type: application/json
{
"messageType": "EncoderLoaded",
"workflowInstanceId": "19e37ccafbec489e843996aca2493eb9",
"encoderIndex": 0,
"encoderId": "encoder_cl_1",
"correlationId" : "00001"
"jobId": "40",
// ... more properties ..
}
The fields that we really care about here are:
messageType
.EncoderLoaded
means that AIDA placed a card in the encoding stationworkflowInstanceId
, this field is populated by AIDA when the job execution starts, and it identifies theworkflow
created to handle the card productionencoderIndex
. Tells us which encoder is holding the cardencoderName
. A name given to the encoder in AIDA Server's configurationcorrelationId
will contain the value of thecorrelation_id
field from theData Exchange Table
if providedjobId
a unique identifier for theJob
(auto generated by AIDA).
You may notice we have 2 identifiers for the Job
. The difference between the 2 is that jobId
is the primary key
for the record in the Data Exchange Table
while the WorkflowInstanceId
is populated by the internal workflow engine as soon as AIDA starts processing the record.
If we are able to parse the request payload, we need to send the HTTP Response
, otherwise the HTTP POST
request on the other end will timeout
HTTP/1.1 200 OK
...
At this point we can do whatever we want with the card. Once we finish we can use the SignalExternalProcessCompleted API endpoint to either resume the process and continue with other operations or reject the card.
Resuming / Rejecting
POST /aida/v1/workfllow-scheduler/signal/external-process-completed HTTP/1.1
Host: machine-ip-address:5000
Accept: application/json
Content-Type: application/json
{
"outcome": "Completed",
"workflowInstance": "19e37ccafbec489e843996aca2493eb9"
}
Here we tell AIDA that we did not ran into problems (Completed
outcome) and it can resume the workflow instance (aka the Job
) with id 19e37ccafbec489e843996aca2493eb9
(we received in the EncoderLoaded
webhook message).
If we instead want to reject the card, we simply return the Faulted
outcome:
POST /aida/v1/workfllow-scheduler/workflows/signal/external-process-completed HTTP/1.1
Host: machine-ip-address:5000
Accept: application/json
Content-Type: application/json
{
"outcome": "Faulted",
"workflowInstance": "19e37ccafbec489e843996aca2493eb9"
}
Handling SchedulerProcessSuspended Message
In the case of recoverable errors, AIDA suspends issuance and publishes the WorfklowSchedulerProcessSuspendedMessage
. Possible error codes are:
FeederEmpty
- The input feeder has no cardsOpenInterlock
- Open interlocks detectedCardJam
- Recoverable only in BOX systems
The controlling application can decide which is the correct behavior in this case. It could notify the operator about the error, and then invoke the ResumeWorkflowScheduler endpoint or stop issuance Gracefuly invoking the StopWorkflowScheduler endpoint.
Feeder empty example
In this example we will see how to handle the FeederEmpty
erorr code, but the same concept applies to other recoverable errors like OpenInterlock
.
If the input feeder is empty when AIDA is trying to start a new job, it will publish the WorkflowSchedulerProcessSuspendedMessage
, which looks like this:
POST /ixla/aida/v1/webhooks HTTP/1.1
Host: webhooks-host:4567
Content-Type: application/json
{
"MessageType": "WorkflowSchedulerStopped",
"ErrorCode": "FeederEmpty",
// ... more properties ...
}
In this case we can load the input feeder with cards, then tell AIDA to retry by invoking the ResumeWorkflowScheduler endpoint.
Resuming suspended workflows
POST /aida/v1/workflow-scheduler/resume HTTP/1.1
Host: machine-ip-address:5000
Accept: application/json
Content-Type: application/json
This endpoint does not require the workflowInstanceId
parameter because it will try to resume ALL suspended workflows in a recoverable error state. In this case order is preserved by the system.
When resuming, AIDA will try to execute the last activity that caused the process to suspend. If it encounters the same error condition, it will simply suspend workflows again and repeat the process.
Graceful stop
Lets suppose we don't want to recover from the FeederEmpty
state, instead we want the machine to finish whatever it was doing and stop. To do this, we can instruct it to stop gracefully by invoking the StopWorkflowScheduler
endpoint with the stopRunningWorkflows
parameter set to false
POST /aida/v1/workflow-scheduler/stop?stopRunningWorkflows=false HTTP/1.1
Host: aida-machine-address:5000
Accept: application/json
Content-Type: application/json
The system will enter the Stopping
state, finish the jobs it was running. When all jobs complete it will publish the WorkflowSchedulerStopped
message with WorkflowSchedulerStopReason set to ManualStop