Best practices
Review these best practices to make sure your webhooks remain secure and function well with your integration
Duplicate event handling
Webhook endpoints may receive the same event more than once, which can result in duplicated event receipts. To prevent this, it's important to make your event processing idempotent. One way to achieve this is by logging the events that you have already processed, and then not processing any events that have already been logged.
Specifically listening to event types your integration requires
To optimize the performance of your webhook endpoints, it's recommended that you configure them to receive only the types of events that are required by your integration. Listening for extra events, or all events, can put undue strain on your server.
You can change the events that a webhook endpoint receives in the PaySG Dashboard.
Exempt webhook route form CSRF protection
For applications using Rails, Django, or another web framework, your site might automatically check that every POST request contains a CSRF token as a security feature to protect you and your users from cross-site request forgery attempts. However, this might also prevent your site from processing legitimate events. In such cases, you might need to exempt the webhooks route from CSRF protection.
Verify events are sent from PaySG
You should verify webhook signatures to confirm that received events are sent from PaySG. PaySG signs webhook events that are sent to your endpoints by including a signature in each event's PaySG-Signature
header. This allows you to verify that the events were sent by PaySG, not by a third party.
To verify webhook signatures, developers can follow these steps:
Extract the signature from the HTTP header called
PaySG-Signature
The value of the header has the format
t=timestamp, v1=signature
. Extract the timestamp and the signatureConcatenate the timestamp and the JSON-stringified HTTP payload, separated by a
.
to form the signature payloadUse HMAC-SHA256 and the symmetric key provided to you by PaySG to verify that the signature is valid
Retrieving your endpoint's secret
For now, they can be obtained through direct request to the PaySG Team. The PaySG team will provide the secret to you when we successfully help you register it.
The secret is a unique value that is used to sign webhook events sent to your endpoint. You will need to use this secret to verify the signature of incoming webhook events.
Preventing replay attacks
To prevent replay attack, PaySG includes a timestamp in the PaySG-Signature header. This timestamp is part of the signed payload and is verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, your e-service should reject the payload.
You can consider having a tolerance between the timestamp included in the header and the current time.
Note that if PaySG retries an event (for example, if your endpoint previously replied with a non-2xx status code), a new signature and timestamp will be generated for the new delivery attempt.
Verifying signatures manually
You can also create a custom solution by following these steps:
Step 1: Extract the timestamp and signatures from the header
The PaySG-Signature
header included in each signed event contains a timestamp and one or more signatures that you must verify. The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature (or signatures). To prevent downgrade attacks, ignore all schemes that are not v1
.
Step 2: Prepare the signed_payload string
The signed_payload
string is created by concatenating:
The timestamp (as a string)
t
The character
.
The actual JSON payload (i.e., the request body).
Step 3: Determine the expected signature
Compute an HMAC with the SHA-256 hash function. Use the endpoint's signing secret as the key, and use the signed_payload
string as the message.
Step 4: Compare the signatures
Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance. To protect against timing attacks, use a constant-time-string comparison to compare the expected signature to each of the received signatures.
Last updated