Verify AWS SNS Signatures in PHP

Verify AWS SNS Signatures in PHP

SNS messages must be verified before processing to prevent spoofing. This guide shows how to validate SigningCertURL, verify signatures (v1/v2), pin TopicArn, and safely confirm subscriptions using the AWS PHP SNS Message Validator. The primary keyword aws sns signature verification php is used in examples and test steps.

You will add middleware that uses AWS’s MessageValidator, pin TopicArn, and fail closed on invalid signatures.

Why SNS signature verification is non-negotiable

SNS messages can be delivered by anyone who can post to your endpoint — signature verification prevents spoofed notifications.

Spoofing risks

Without verification, attackers can send fake notifications that trigger downstream workflows.

TopicArn pinning

Pin expected TopicArn values to avoid accepting messages from unauthorized topics.

Micro checklist:

  • Always verify signatures before processing.
  • Pin TopicArn where possible.
  • Confirm subscription messages only after verification.

SNS signature versions + best practices

Be aware of signature versions and always fetch signing certs over HTTPS from AWS domains.

v2 (SHA256) vs v1 (SHA1)

Prefer v2 (SHA256) when available; validator libraries support both.

Always fetch signing cert via HTTPS + validate domain

Only fetch signing certificates from sns.amazonaws.com or other trusted AWS domains.

Reject unexpected TopicArn

If TopicArn does not match your expected value, reject the message.

Common gotcha: Accepting arbitrary SigningCertURL values without domain validation exposes you to certificate substitutions.

Use the official library to validate messages and handle SubscriptionConfirmation safely.

Composer install + prerequisites

1
composer require aws/aws-php-sns-message-validator

Middleware pattern in Laravel

Use raw JSON string to build Message objects and validate with MessageValidator.

Handling SubscriptionConfirmation safely

Confirm subscriptions only after MessageValidator returns valid.

Suggested diagram: Flowchart showing raw JSON → Message::fromJsonString → MessageValidator::isValid → accept/reject.

Local testing strategy

Fail closed in local tests and use real SNS publishes for end-to-end verification.

“Fail closed” curl test

Reject invalid signatures and return 401 in test cases so test coverage mirrors production.

End-to-end test via real SNS publish

Publish a test message in the console or CLI and validate it arrives and is processed after signature verification.

Required code snippets

B) Middleware (Laravel) using raw body

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Aws\Sns\Message;
use Aws\Sns\MessageValidator;

class VerifyAwsSnsSignature
{
    public function handle(Request $request, Closure $next)
    {
        $raw = $request->getContent();

        // Build message from the raw JSON string (Laravel-friendly)
        $message = Message::fromJsonString($raw);

        // Optional but recommended: TopicArn pinning
        $expectedTopicArn = config('services.aws.sns_topic_arn');
        if (!empty($expectedTopicArn) && ($message['TopicArn'] ?? null) !== $expectedTopicArn) {
            return response()->json(['error' => 'unexpected topic'], 401);
        }

        $validator = new MessageValidator();

        if (!$validator->isValid($message)) {
            return response()->json(['error' => 'invalid sns signature'], 401);
        }

        return $next($request);
    }
}

C) Apply to your SES SNS endpoint

1
2
Route::post('/webhooks/aws/sns/ses', [AwsSnsSesController::class, 'handle'])
    ->middleware(['aws.sns.signature']);

Test steps (curl + expected response)

  1. Invalid signature (should fail closed)
1
2
3
curl -i -X POST "https://YOUR-APP.test/api/webhooks/aws/sns/ses" \
  -H "Content-Type: application/json" \
  -d '{"Type":"Notification","Message":"hello","Signature":"bogus","SigningCertURL":"https://evil.example.com/cert.pem"}'

Expected: 401 invalid sns signature

  1. Real publish test
  • Publish a test message to the SNS topic (console or CLI). Your endpoint should return 204 and process it once signature validation passes.

Common failure modes

  1. Not verifying SigningCertURL trust (accepting attacker-provided cert URL).
  2. Not pinning TopicArn (accepting messages from other topics).
  3. Validating after processing (side effects before auth).
  4. Reading/mutating body before validation (use raw JSON string path).
  5. Ignoring SubscriptionConfirmation (you never confirm subscription, so no notifications arrive).

Verify once, trust downstream: after signature checks pass, forward events into SendPromptly and inspect the run details. Open Sample Project · Open Message Log

Conclusion

  • Use AWS’s MessageValidator library to validate SNS messages.
  • Pin TopicArn and validate SigningCertURL domains.
  • Fail closed on invalid signatures and confirm subscriptions securely.
  • Test with a real SNS publish for end-to-end verification.