Payment Webhook Handler

Every time there is a successful payment for your store, Teya makes a call to the webhook URLs created for that store.

The webhook request includes an x-teya-signature header that must be verified to ensure the call was made by Teya.

The public key for the signature was returned at the webhook creation from the Teya Business Portal. The public key associated to a specific webhook can be retrieved at any time from the Teya Business Portal.

We use the SHA256withRSA algorithm.

For the signature verification, you should use the entire payload received in the call made from Teya.

Webhook Payload Fields

Field Description Example
event Event type name payment.succeeded.v1
timestamp Time the event occurred (ISO 8601) 2025-10-01T08:36:06Z
data.transaction_id Unique identifier for the transaction, which can be used for refunds and receipt creation. tr_01999eea-03f5-78ae-962b-97ff946aad4d
data.authentication_id Unique identifier for the authentication ee8125ef-501f-4051-be2f-6714565aa0f1
data.payment_method.type Payment method type CARD
data.payment_method.brand Card brand MASTERCARD
data.payment_method.last4 Last 4 digits of card number 0000
data.amount Payment amount, same as used in the session, payment link or transaction creation {"value": 50, "currency": "GBP"}
data.status Payment status, we only send for successful payments currently SUCCESS
data.session_id The payment session identifier, if applicable, can be used to identify the order. 01999ee9-c5d6-791f-ba20-4156434915f6
data.payment_link_id The payment link identifier, if applicable. 20eb276c-5b6b-4474-a5a1-49bcd6193148
data.authorisation_code Card authorisation code 471274
data.merchant_reference Reference used when creating the session. my-order-123
data.token_response Optional. Contains details of the token created for the card. null when no token was created for the payment. token_type is CARD_TOKEN for a token created from card details and WALLET_TOKEN for one created from Apple Pay; send it back when charging the token. {"token_id": "5f7b1a3a-3b7a-4b1a-9b1a-3a3b7a4b1a9b", "token_status": "SUCCESS", "token_type": "CARD_TOKEN"}

Example Payload

Headers

user-agent: Teya-Webhooks
x-teya-signature: gOzDXhS7M8afuYjYx9EGTjOUXraexOijxN7V9f+hlourPwrZ6e6nrrs6mRlFb5xLyfCDIOneKUORdNNX9tFMPdTruPwLnjE6OhZnv9IKSJZ4B1C0aewED2koC4taO9Ye9sHqmK3ZRSP9c31CG2KCV3+mGa92UeDqfWtc+w6R8z4mr98yuvi76nHFjdTG59UIRgAjItr9vwo7HLbshMH0xe1MTmuaDx3D5pDJqmQAE0/kMptpbURwLU6oegaaX0CZ/4MB5A5Wq+PgdDxUxJ6A5N1s1bqtn7hs9nIgSfSvTCIKpOXjZeMQtw+8zT9CEEJ7ASwMAEk0LlN7v+2thutMqA==

Body

{
  "event": "payment.succeeded.v1",
  "timestamp": "2025-12-02T12:04:22Z",
  "data": {
    "transaction_id": "tr_019adef3-0d07-7543-9b74-efa2d5b6fa2a",
    "payment_method": {
      "type": "CARD",
      "brand": "MASTERCARD",
      "last4": "4065"
    },
    "amount": {
      "value": 3434,
      "currency": "GBP"
    },
    "status": "SUCCESS",
    "session_id": "019adef2-e5b3-7e00-9fe1-61a5a01f3eef",
    "payment_link_id": "019adef2-e00a-75f2-8d6d-490b345981b2",
    "authentication_id": "019adef3-0cc3-7645-9d87-5cd0357e8509",
    "authorisation_code": "776761",
    "merchant_reference": "asdasd",
    "token_response": null
  }
}
Test your signature validation
Use the Webhook Signature Validator tool to verify your implementation is correctly validating webhook signatures before going live.

Validate Signatures

Every webhook sent by Teya includes an x-teya-signature header containing a cryptographic signature. Validating this signature ensures:

  • Authenticity: The webhook was genuinely sent by Teya
  • Integrity: The payload has not been modified in transit
  • Security: Protection against replay attacks and forged requests

Signature Algorithm

Teya uses SHA256withRSA (RSASSA-PKCS1-v1_5 with SHA-256) for webhook signatures. The signature is Base64 encoded.

Validation Process

Step 1: Extract the Signature

Extract the x-teya-signature header from the incoming webhook request:

x-teya-signature: gOzDXhS7M8afuYjYx9EGTjOUXra...

Step 2: Get the Raw Request Body

Use the entire raw request body exactly as received. Do not parse, format, or modify the JSON in any way.

Important: Use the raw bytes of the request body, not a parsed and re-serialized JSON object. Any modification to whitespace or field ordering will cause validation to fail.

Step 3: Retrieve Your Public Key

The public key for your webhook was provided when you created the webhook in the Teya Business Portal. You can retrieve it at any time from the portal under your webhook configuration.

Step 4: Verify the Signature

Decode the Base64 signature and verify it against the raw payload using your public key.

Delivery and Retries

Teya sends each event as an HTTP POST request to your webhook URL. Your endpoint must reply with a 2xx status code within 30 seconds to confirm it received the event. Teya also gives up on connecting to your server after 5 seconds.

When a delivery fails

Teya treats a delivery as failed when:

  • Your endpoint returns any status code that is not 2xx (for example 4xx or 5xx)
  • Your endpoint does not reply within 30 seconds
  • Teya cannot connect to your server (for example DNS failure, connection refused or TLS error)

Retry schedule

When a delivery fails, Teya retries it with increasing delays. Each event is attempted up to 6 times in total:

Attempt Delay after previous attempt Time after event
1st Immediately 00:00
2nd 5 minutes 00:05
3rd 15 minutes 00:20
4th 1 hour 01:20
5th 2 hours 03:20
6th 6 hours 09:20

If the 6th attempt also fails, Teya stops retrying and the event is not delivered. If you deactivate a webhook, any pending retries for it are dropped.

Handle duplicate events
A retry can reach you even when you processed the event but your reply did not arrive in time (for example your server timed out after saving the payment). Use data.transaction_id to detect and ignore events you have already processed.

Tips for reliable delivery

  • Reply first, process later: return 2xx as soon as you have stored the event, then do any slow work (sending emails, updating stock) asynchronously
  • Do not reply 2xx when you could not store the event: return a 5xx instead so Teya retries it
  • Keep your endpoint idempotent: processing the same event twice must not create duplicate orders or refunds

Troubleshooting

Common Issues

Issue Cause Solution
Signature always fails Modified payload Use the raw request body, not parsed JSON
Invalid key format Wrong key format Ensure key is in PEM format with correct headers
Encoding errors Character encoding Use UTF-8 encoding consistently
Missing signature header Framework stripping headers Check your web framework's header handling

Debugging Tips

  1. Log the raw payload: Print the exact bytes received to verify no modification occurred
  2. Check header case: Some frameworks lowercase headers (x-teya-signature vs X-Teya-Signature)
  3. Verify key format: Ensure newlines are preserved in the PEM key

Security Best Practices

  • Always validate signatures before processing webhook data
  • Store keys securely using environment variables or a secrets manager
  • Return quickly with a 2xx response to avoid webhook retries (see Delivery and Retries), then process asynchronously
  • Log validation failures for security monitoring