Learnixo
Back to blog
AI Systemsintermediate

Azure Functions Triggers — HTTP, Timer, Service Bus, and Blob

Use Azure Functions triggers in .NET: HTTP triggers for APIs, Timer triggers for scheduled jobs, Service Bus triggers for event processing, and Blob triggers for file processing workflows.

Asma Hafeez KhanMay 16, 20265 min read
Azure FunctionsServerlessTriggers.NETAzure
Share:𝕏

Trigger Types Overview

HTTP Trigger:
  → Invoked by an HTTP request
  → Use for: lightweight APIs, webhooks, FHIR callbacks
  → Cold start: first invocation after idle may be slow

Timer Trigger:
  → Invoked on a CRON schedule
  → Use for: nightly report generation, prescription expiry jobs, data sync
  → Does NOT fire missed runs by default — use WEBSITE_TIME_ZONE for local time

Service Bus Trigger:
  → Invoked when a message arrives on a queue or topic subscription
  → Use for: processing integration events, outbox relay, notification dispatch
  → At-least-once delivery — your handler must be idempotent

Blob Trigger:
  → Invoked when a blob is created or modified in a container
  → Use for: processing uploaded patient documents, PDF generation
  → Note: can lag up to 10 minutes for large containers — use Event Grid trigger for real-time

HTTP Trigger

C#
// Isolated Worker model (.NET 8) — preferred over in-process
// NuGet: Microsoft.Azure.Functions.Worker, Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore

public class PatientWebhookFunction
{
    private readonly IMediator _mediator;

    public PatientWebhookFunction(IMediator mediator) => _mediator = mediator;

    [Function("PatientAdmissionWebhook")]
    public async Task<IActionResult> RunAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "webhooks/patient-admission")]
        HttpRequest req,
        CancellationToken ct)
    {
        var body = await req.ReadFromJsonAsync<PatientAdmissionWebhookPayload>(ct);
        if (body is null) return new BadRequestResult();

        var command = new ProcessPatientAdmissionCommand(
            body.PatientId, body.WardId, body.AdmittedAt);

        var result = await _mediator.Send(command, ct);

        return result.IsSuccess
            ? new OkResult()
            : new BadRequestObjectResult(result.Error.Message);
    }
}

Timer Trigger — Scheduled Jobs

C#
// Expire pending prescriptions that have been waiting more than 48 hours
public class ExpirePrescriptionsFunction
{
    private readonly IPrescriptionRepository _repository;
    private readonly IClock                  _clock;

    public ExpirePrescriptionsFunction(
        IPrescriptionRepository repository, IClock clock)
    {
        _repository = repository;
        _clock      = clock;
    }

    // CRON expression: "0 */6 * * *" = every 6 hours
    // "0 0 2 * * *" = daily at 2:00 AM UTC
    [Function("ExpirePrescriptions")]
    public async Task RunAsync(
        [TimerTrigger("0 */6 * * *")] TimerInfo timerInfo,
        CancellationToken ct)
    {
        if (timerInfo.IsPastDue)
        {
            // The function missed a scheduled invocation (e.g., function was stopped)
            // Decide: process anyway or skip
        }

        var cutoff = _clock.UtcNow.AddHours(-48);
        var expiring = await _repository.GetPendingOlderThanAsync(cutoff, ct);

        foreach (var prescription in expiring)
        {
            prescription.Expire();
            await _repository.SaveAsync(prescription, ct);
        }
    }
}

Service Bus Trigger — Event Processing

C#
// Process PrescriptionCreated integration events from Service Bus
public class PrescriptionCreatedFunction
{
    private readonly IPharmacyDispenseService _dispenseService;

    public PrescriptionCreatedFunction(IPharmacyDispenseService dispenseService) =>
        _dispenseService = dispenseService;

    [Function("ProcessPrescriptionCreated")]
    public async Task RunAsync(
        [ServiceBusTrigger(
            topicName:        "prescriptioncreated-events",
            subscriptionName: "pharmacy-service",
            Connection:       "ServiceBus")]
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions  messageActions,
        CancellationToken         ct)
    {
        var @event = message.Body.ToObjectFromJson<PrescriptionCreatedIntegrationEvent>();

        try
        {
            await _dispenseService.CreateDispenseTaskAsync(@event.PrescriptionId, ct);
            await messageActions.CompleteMessageAsync(message, ct);
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            // On failure: dead-letter instead of letting Service Bus retry
            // Include reason for dead-lettering (aids investigation)
            await messageActions.DeadLetterMessageAsync(
                message,
                deadLetterReason:           "ProcessingFailed",
                deadLetterErrorDescription: ex.Message,
                ct);
        }
    }
}

Blob Trigger — File Processing

C#
// Process uploaded patient documents (e.g., scan and index content)
public class PatientDocumentProcessorFunction
{
    private readonly IDocumentIndexingService _indexing;

    public PatientDocumentProcessorFunction(IDocumentIndexingService indexing) =>
        _indexing = indexing;

    [Function("ProcessPatientDocument")]
    public async Task RunAsync(
        [BlobTrigger("patient-documents/{patientId}/{name}", Connection = "AzureStorage")]
        Stream documentStream,
        string patientId,
        string name,
        CancellationToken ct)
    {
        await _indexing.IndexDocumentAsync(
            patientId:    Guid.Parse(patientId),
            documentName: name,
            content:      documentStream,
            ct:           ct);
    }
}

// Note on Blob Trigger latency:
// If real-time processing is required (under 1 minute), use:
// → Event Grid trigger: notified immediately when blob is uploaded
// Blob trigger polled by Azure Functions every ~10 minutes for large containers

Program.cs for Isolated Worker

C#
// Azure Functions Isolated Worker host setup
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureAppConfiguration(config =>
    {
        config.AddEnvironmentVariables();
        // Add Key Vault in production:
        if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("KeyVaultUri")))
            config.AddAzureKeyVault(
                new Uri(Environment.GetEnvironmentVariable("KeyVaultUri")!),
                new DefaultAzureCredential());
    })
    .ConfigureServices((context, services) =>
    {
        var config = context.Configuration;

        services.AddDbContext<PrescriptionsDbContext>(opts =>
            opts.UseSqlServer(config.GetConnectionString("Clinical")));

        services.AddMediatR(cfg =>
            cfg.RegisterServicesFromAssembly(typeof(ProcessPatientAdmissionCommand).Assembly));

        services.AddSingleton<IClock, SystemClock>();
        services.AddScoped<IPrescriptionRepository, PrescriptionRepository>();
    })
    .Build();

await host.RunAsync();

Production issue I've seen: A Timer Trigger function ran a nightly data sync at 2:00 AM. The function was deployed in a Consumption plan. Due to idle timeout, the function was deallocated at 1:58 AM — just before its scheduled run. Azure Functions detected the "missed" execution 10 minutes later (when the next polling cycle ran), but timerInfo.IsPastDue was true. The function code checked IsPastDue and skipped the run ("we'll catch it at 2:00 AM tomorrow"). The data sync didn't run for 3 consecutive nights before a monitoring alert caught it. Fix: use a Premium or Dedicated plan (no cold start/idle timeout) for Timer Triggers with strict scheduling requirements, and handle IsPastDue = true by running the job, not skipping it.


Key Takeaway

Azure Functions triggers route invocations to your code: HTTP (request-driven), Timer (scheduled), Service Bus (event-driven), Blob (file-driven). Use the Isolated Worker model (.NET 8) — not the legacy in-process model. For Service Bus triggers, complete messages manually with ServiceBusMessageActions and dead-letter on unrecoverable failures. Handle timerInfo.IsPastDue = true — decide explicitly whether to run or skip missed executions. Use Key Vault + Managed Identity for all secrets in function apps.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.